MQI uses term_to_json/2 to convert query answers to JSON and I want to link the documentation. But, unless I (and based on the comments under the docs, LogicalCaptain) am really missing something, the documentation for term_to_json/2 is pretty far off.
The doc currently says this (I’ve put my own tests indented under each bullet). As you can see from the tests, the docs are not really even close:
Convert any general Prolog term into a JSON term. Prolog lists
are treated in a special way. Also, JSON terms are not converted. Mapping:
* Variable: `{"type":"var", "name":<string>}`
?- term_to_json(X, JSON).
JSON = "_".
* Atom: `{"type":"atom", "value":<string>}`
?- term_to_json(a, JSON).
JSON = a.
* Integer: `{"type":"integer", "value":<integer>}`
?- term_to_json(1, JSON).
JSON = 1.
* Float: `{"type":"float", "value":<float>}`
?- term_to_json(1.1, JSON).
JSON = 1.1.
* List: JSON array
?- term_to_json([A, 1, "a"], JSON).
JSON = ["_", 1, "a"].
* Dict: a JSON object. Values are processed recursively. (the tag is ignored)
?- term_to_json(tag{a:A}, JSON).
JSON = tag{a:"_"}.
* `json([Key=Value, ...])`: a JSON object Values are processed recursively.
# See comments at bottom on why this fails...
?- term_to_json(json([a=b]), JSON).
false.
* compound: `{"type":"compound", "functor":<string>, "args":<array>}`
?- term_to_json(my_term(A, "one"), JSON).
JSON = json{args:["_", "one"], functor:my_term}.
Based on the tests above and looking at the code, it should say this:
Convert any general Prolog term into a JSON term which can be
used with any of the json_write, json_write_dict, json_read,
json_read_dict predicates. It generates the JSON format used by default in
pengines and MQI.
While library(http/json_convert) allows for
a custom mapping of Prolog terms to JSON, this
predicate gives a default mapping that is not changeable. For example:
?- term_to_json(my_predicate(a, "b", 1, X, @(true)), JSON).
JSON = json{args:[a, "b", 1, "_", true], functor:my_predicate}.
Mapping:
* Variable: JSON = "_"
* Atom: JSON = Atom
* String: JSON = String
* Integer >= -(2**31), Integer < 2**31: JSON = Integer
* Integer < -(2**31), Integer > 2**31: atom_number(JSON, Integer)
* Float: JSON = Float
* @(JSON_Term) where JSON_Term is `true`, `false` or `null`: JSON_Term
* List: List, list items are processed recursively using term_to_json/2
* Dict: Dict, Values are processed recursively using term_to_json/2,
the tag is retained
* Compound: Compound =.. [F|Args], JSON = json{functor:F, args:Args}, Args
are processed recursively using term_to_json/2
* `json([Key=Value, ...])`: Fails. Term must not be an existing
`json(List)` term (and must not recursively contain any).
Notes:
* If Term is (or recursively contains) existing JSON terms (i.e. terms of
the form `json(List)`, term_to_json fails.
* Valid JSON predicates (i.e. predicates of the form `json(List)`)
which either contain dicts that use integers as keys or contain
objects like streams will fail when written to text by the
`json_write` and `json_write_dict` predicates. Neither can
be represented in JSON text. term_to_json will generate these
predicates successfully, but they will fail when writing them to text.
Given that terms of the form json/1 were supposed to work (but I believe never have), I propose NOT doing the fix below, but fixing the docs as above to describe the current behavior…
# Description of why term_to_json/2 fails when processing json(List) terms
term_to_json(Term, Bindings, JSON) :-
findall(X,
( maplist(bind_var, Bindings),
numbervars(Term, 0, _, [singletons(true)]),
to_json(Term, X)
),
# Because JSON is in a list, it forces findall to fail if there is more than one item
# because a cut is missing below (in to_json), multiple items are returned for json/1 terms
# and thus it fails
[JSON]).
to_json(json(Pairs0), Term) :-
must_be(list, Pairs0),
# this should be added to make this work as described
# in the comment above:
# !,
maplist(pair_value_to_json_ex, Pairs0, Pairs),
dict_pairs(Term, json, Pairs).