Did you know ... | Search Documentation: |
Supporting JSON |
From http://json.org, " JSON (JavaScript Object Notation) is a lightweight data-interchange format. It is easy for humans to read and write. It is easy for machines to parse and generate. It is based on a subset of the JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999. JSON is a text format that is completely language independent but uses conventions that are familiar to programmers of the C-family of languages, including C, C++, C#, Java, JavaScript, Perl, Python, and many others. These properties make JSON an ideal data-interchange language."
Although JSON is nowadays used a lot outside the context of web
applications, SWI-Prolog's support for JSON started life as part of the
HTTP package. SWI-Prolog supports two Prolog representations for JSON
terms. The first and oldest map JSON objects to a term
json(PropertyList)
and use the @
functor to
disambiguate e.g. null
from the string "null"
,
leading to @(null)
. As of SWI-Prolog version 7, JSON
objects may be represented using dict objects and JSON strings
using Prolog strings. Predicates following this convention are suffixed
with _dict
, e.g. json_read_dict/2.
For example, given the JSON document
{ "name": "Bob", "children": ["Mary", "John"], "age":42, "married": true }
we get either (using json_read/2):
json([name='Bob', children=['Mary', 'John'], age=42, married= @(true)]).
or (using json_read_dict/2):
_{age:42, children:["Mary", "John"], married:true, name:"Bob"}
The SWI-Prolog JSON interface consists of three libraries:
library(http/json)
provides support for the core JSON
object serialization and parsing.library(http/json_convert)
converts between the primary
representation of JSON terms in Prolog and more application oriented
Prolog terms. E.g. point(X,Y)
vs. object([x=X,y=Y])
.library(http/http_json)
hooks the conversion libraries
into the HTTP client and server libraries.
http_json.pl
links JSON to the HTTP client and server
modules. json_convert.pl
converts JSON Prolog terms to more
comfortable terms.This module supports reading and writing JSON objects. This library supports two Prolog representations (the new representation is only supported in SWI-Prolog version 7 and later):
json(NameValueList)
, a JSON string as an
atom and the JSON constants null
, true
and
false
as @(null), @(true) and @false.null
, true
and false
.atom
(default),
string
, codes
or chars
.
json(NameValueList)
,
where NameValueList is a list of Name=Value. Name is an atom created
from the JSON string.true
and false
are
mapped -like JPL- to @(true) and @(false).null
is mapped to the Prolog term
@(null)Here is a complete example in JSON and its corresponding Prolog term.
{ "name":"Demo term", "created": { "day":null, "month":"December", "year":2007 }, "confirmed":true, "members":[1,2,3] }
json([ name='Demo term', created=json([day= @null, month='December', year=2007]), confirmed= @true, members=[1, 2, 3] ])
The following options are processed:
null
. Default
@(null)true
. Default
@(true)false
. Default
@(false)error
):
==
error
, throw
an unexpected end of file syntax error
Returning an status term is required to process
Concatenated
JSON. Suggested values are @(eof)
or end_of_file
.
atom
.
The alternative is string
, producing a packed string
object. Please note that codes
or chars
would
produce ambiguous output and are therefore not supported.Values can be of the form #(Term), which causes Term to be stringified if it is not an atom or string. Stringification is based on term_string/2.
Rational numbers are emitted as floating point numbers. The hook json_write_hook/4 can be used to realize domain specific alternatives.
The version 7 dict type is supported as well. Optionally, if
the dict has a tag, a property "type":"tag" can be added to the
object. This behaviour can be controlled using the tag
option (see below). For example:
?- json_write(current_output, point{x:1,y:2}). { "x":1, "y":2 }
?- json_write(current_output, point{x:1,y:2}, [tag(type)]). { "type":"point", "x":1, "y":2 }
In addition to the options recognised by json_read/3, we process the following options are recognised:
true
(default false
), serialize unknown
terms and print them as a JSON string. The default raises a type error.
Note that this option only makes sense if you can guarantee that the
passed value is not an otherwise valid Prolog reporesentation of a
Prolog term.
If a string is emitted, the sequence </
is emitted as
<\/
. This is valid JSON syntax which ensures that JSON
objects can be safely embedded into an HTML <script>
element.
Note that this hook is shared by all users of this library. It is generally adviced to map a unique compound term to avoid interference with normal output.
State | and Options are opaque handles to the current output state and settings. Future versions may provide documented access to these terms. Currently it is adviced to ignore these arguments. |
true
, false
and null
constants.
true
, false
and null
are
represented using these Prolog atoms.type
field in an object assigns a tag for
the dict.
The predicate json_read_dict/3
processes the same options as
json_read/3, but with different
defaults. In addition, it processes the tag
option. See json_read/3
for details about the shared options.
tag
option does not
apply.null
.true
.false
string
.
The alternative is atom
, producing a packed string object.atom
,
string
or codes
.
null
.
Conversion to Prolog could translate @null into a variable if the
desired type is not any
. Conversion to JSON could map
variables to null
, though this may be unsafe. If the Prolog
term is known to be non-ground and JSON @null is a sensible mapping, we
can also use this simple snipit to deal with that fact.
term_variables(Term, Vars), maplist(=(@null), Vars).
The idea behind this module is to provide a flexible high-level
mapping between Prolog terms as you would like to see them in your
application and the standard representation of a JSON object as a Prolog
term. For example, an X-Y point may be represented in JSON as {"x":25, "y":50}
.
Represented in Prolog this becomes json([x=25,y=50])
, but
this is a pretty non-natural representation from the Prolog point of
view.
This module allows for defining records (just like library(record)
)
that provide transparent two-way transformation between the two
representations.
:- json_object point(x:integer, y:integer).
This declaration causes prolog_to_json/2 to translate the native Prolog representation into a JSON Term:
?- prolog_to_json(point(25,50), X). X = json([x=25, y=50])
A json_object/1 declaration
can define multiple objects separated by a comma (,), similar to the dynamic/1
directive. Optionally, a declaration can be qualified using a module.
The conversion predicates
prolog_to_json/2 and json_to_prolog/2
first try a conversion associated with the calling module. If not
successful, they try conversions associated with the module user
.
JSON objects have no type. This can be solved by adding an
extra field to the JSON object, e.g. {"type":"point", "x":25, "y":50}
.
As Prolog records are typed by their functor we need some notation to
handle this gracefully. This is achieved by adding +Fields to the
declaration. I.e.
:- json_object point(x:integer, y:integer) + [type=point].
Using this declaration, the conversion becomes:
?- prolog_to_json(point(25,50), X). X = json([x=25, y=50, type=point])
The predicate json_to_prolog/2 is often used after http_read_json/2 and prolog_to_json/2 before reply_json/1. For now we consider them separate predicates because the transformation may be too general, too slow or not needed for dedicated applications. Using a separate step also simplifies debugging this rather complicated process.
f(Name, Type, Default, Var)
,
ordered by Name. Var is the corresponding variable in Term.library(record)
. E.g.
?- json_object point(x:int, y:int, z:int=0).
The type arguments are either types as know to library(error)
or functor names of other JSON objects. The constant any
indicates an untyped argument. If this is a JSON term, it becomes
subject to json_to_prolog/2.
I.e., using the type
list(any)
causes the conversion to be executed on each
element of the list.
If a field has a default, the default is used if the field is not
specified in the JSON object. Extending the record type definition,
types can be of the form (Type1|
Type2). The type
null
means that the field may not be present.
Conversion of JSON to Prolog applies if all non-defaulted arguments can be found in the JSON object. If multiple rules match, the term with the highest arity gets preference.
true
, on
or 1
for @true
and one of false
, fail
, off
or 0
for @false.
:-
json_object/1
declarations. If a json_object/1
declaration declares a field of type
boolean
, commonly used thruth-values in Prolog are
converted to JSON booleans. Boolean translation accepts one of true
,
on
, 1
, @true, false
, fail
, off
or 0
, @false.
type_error(json_term, X)
:-
json_object/1
declarations. An efficient transformation is non-trivial, but we rely on
the assumption that, although the order of fields in JSON
terms is irrelevant and can therefore vary a lot, practical applications
will normally generate the JSON objects in a consistent
order.
If a field in a json_object is declared of type boolean
,
@true and @false are translated to true
or false
,
the most commonly used Prolog representation for truth-values.
json.pl
describes how JSON objects are represented in
Prolog terms. json_convert.pl
converts between more natural Prolog
terms and json terms.
Most code doesn't need to use this directly; instead use
library(http/http_server)
, which combines this library with
the typical HTTP libraries that most servers need.
This module adds hooks to several parts of the HTTP libraries, making them JSON-aware. Notably:
application/json
and
application/jsonrequest
content to a JSON term.post(json(Term))
to issue a POST request with JSON content.
Accept
header prefers application/json over
text/html.Typically JSON is used by Prolog HTTP servers. This module supports two JSON representations: the classical representation and the new representation supported by the SWI-Prolog version 7 extended data types. Below is a skeleton for handling a JSON request, answering in JSON using the classical interface.
handle(Request) :- http_read_json(Request, JSONIn), json_to_prolog(JSONIn, PrologIn), <compute>(PrologIn, PrologOut), % application body prolog_to_json(PrologOut, JSONOut), reply_json(JSONOut).
When using dicts, the conversion step is generally not needed and the code becomes:
handle(Request) :- http_read_json_dict(Request, DictIn), <compute>(DictIn, DictOut), reply_json(DictOut).
This module also integrates JSON support into the http client
provided by http_client.pl
. Posting a JSON query and
processing the JSON reply (or any other reply understood by http_read_data/3)
is as simple as below, where Term is a JSON term as described in json.pl
and reply is of the same format if the server replies with JSON.
..., http_post(URL, json(Term), Reply, [])
term
or dict
. If
the value is dict
,
json_read_dict/3 is used.MediaType | is a term Type/SubType, where both Type and SubType are atoms. |
http_post(URL, json(Term), Reply, Options) http_post(URL, json(Term, Options), Reply, Options)
If Options are passed, these are handed to json_write/3. In addition, this option is processed:
dict
, json_write_dict/3
is used to write the output. This is default if json(Dict)
is passed.term
(default) to generate a classical Prolog term
or dict
to exploit the SWI-Prolog version 7 data type
extensions. See json_read_dict/3.domain_error(mimetype, Found)
if the mimetype is not
known (see json_type/1). domain_error(method, Method)
if the request method is not
a POST
, PUT
or PATCH
.Content-type
is application/json; charset=UTF8
. charset=UTF8
should not be required because JSON is defined to be UTF-8 encoded, but
some clients insist on it.term
(classical json representation) or dict
to use the new dict representation. If omitted and Term is a dict, dict
is assumed. SWI-Prolog Version 7.