Json_dict/2 - Helpful for learning how to use JSON with SWI-Prolog dict

The predicate json_dict/2 was created to understand some of the finer intricacies for consistently converting JSON to an SWI-Prolog dict and/or converting an SWI-Prolog dict to JSON . It is not meant to be used directly in code, it is for learning.

json_dict(Json0,Dict1) :-
    % mode(+,-)
    nonvar(Json0),
    var(Dict1), !,
    % is_json_term/1 expects an atom and not a term
    % so convert term to an atom.
    term_to_atom(Json0,Json_atom0),
    is_json_term(Json_atom0),
    % When using atom_json_dict/3 to create a dict, use the option value_string_as(atom) to get a consistent tractable representation.
    atom_json_dict(Json_atom0,Dict0,[value_string_as(atom)]),
    atom_json_dict(Json_atom1,Dict0,[]),
    % When using atom_json_dict/3 to create a dict, use the option value_string_as(atom) to get a consistent tractable representation.
    atom_json_dict(Json_atom1,Dict1,[value_string_as(atom)]),
    is_dict(Dict1),
    % Since atoms and strings can be used interchangeably for most SWI-Prolog built-in predicate arguments
    % one can think of term_string/2 as term_atom/2.
    % This can be used as the reverse of term_to_atom/2, (think atom_to_term/2)
    term_string(Json1,Json_atom1),
    % =@= used to allow for difference in system generated tags, e.g. _65108{} and _36929{} are the same.
    assertion( Dict0 =@= Dict1 ),
    assertion( Json0 == Json1 ).
json_dict(Json1,Dict0) :-
    % mode(-,+)
    var(Json1),
    nonvar(Dict0), !,
    % atom_json_dict/3 will accept some values that are not valid dicts, e.g. {}
    % Since the goal is to consistently convert the values, only valid dicts are allowed.
    is_dict(Dict0),
    atom_json_dict(Json_atom0,Dict0,[]),
    % When using atom_json_dict/3 to create a dict, use the option value_string_as(atom) to get a consistent tractable representation.
    atom_json_dict(Json_atom0,Dict1,[value_string_as(atom)]),
    atom_json_dict(Json_atom1,Dict1,[]),
    is_json_term(Json_atom1),
    % Since atoms and strings can be used interchangeably for most SWI-Prolog built-in predicate arguments
    % one can think of term_string/2 as term_atom/2.
    % This can be used as the reverse of term_to_atom/2, (think atom_to_term/2)
    term_string(Json0,Json_atom0),
    term_string(Json1,Json_atom1),
    % =@= used to allow for difference in system generated tags, e.g. _65108{} and _36929{} are the same.
    assertion( Dict0 =@= Dict1 ),
    assertion( Json0 == Json1 ).
json_dict(Json0,Dict0) :-
    % mode(+,+)
    nonvar(Json0),
    nonvar(Dict0), !,
    % check mode(+,-)
    json_dict(Json0,Dict1),
    % check mode(-,+)
    json_dict(Json1,Dict0),
    assertion( Dict0 =@= Dict1 ),
    assertion( Json0 == Json1 ).
json_dict(Json,Dict) :-
    format('Invalid input:\n   Json: ~w\n   Dict: ~w~n',[Json,Dict]).

Notes (Click triangle to exapand)

SWI-Prolog dicts are for holding key-value pairs so an SWI-Prolog dict can only represent JSON starting as an object, an SWI-Prolog dict can not represent some stand alone JSON values like [].


atom_json_dict/3 will accept '[]' as the first argument and correctly convert it to a term but the term is not a valid SWI-Prolog dict.
e.g.

?- json:atom_json_dict('[]',Dict,[]).
Dict = [].
?- json:atom_json_dict('[]',Dict,[]),is_dict(Dict).
false.

{} is not a SW-Prolog dict however atom_json_dict/2 will correctly convert it to an SWI-Prolog dict.
_{} is a dict.
e.g.

?- Json = '{}',json:atom_json_dict(Json,Dict,[]),is_dict(Dict).
Json = {},
Dict = _19260{}.

When reading a JSON file using json_read_dict/3 one may find that converting the internal representation into atoms using option value_string_as(atom) works more intuitively with the built-in SWI-Prolog predicates.


SWI-Prolog dicts allow for numbers as keys but JSON only allows for strings as keys.
If an SWI-Prolog dict uses a number for a key then json_write_dict/3 will throw an error.
e.g.

?- Dict = _{ 1:"a" },json:json_write_dict(current_output,Dict).
 {
 ERROR: Type error: `text' expected, found `1' (an integer)

is_json_term/1 will correctly process the JSON {"key":value} as an atom and not as a term.
e.g.

?- json:is_json_term({"key":value}).
false.
?- json:is_json_term('{"key":value}').
true.

Parsing JSON is a Minefield
Why JSON isn’t a good configuration language


I wanted to save this somewhere related to SWI-Prolog and JSON this looked like an easy to find location.

Tips on Adding JSON Output to Your CLI App