View source with formatted comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        J.Wielemaker@vu.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  2007-2023, University of Amsterdam
    7                              VU University Amsterdam
    8                              CWI, Amsterdam
    9                              SWI-Prolog Solutions b.v.
   10    All rights reserved.
   11
   12    Redistribution and use in source and binary forms, with or without
   13    modification, are permitted provided that the following conditions
   14    are met:
   15
   16    1. Redistributions of source code must retain the above copyright
   17       notice, this list of conditions and the following disclaimer.
   18
   19    2. Redistributions in binary form must reproduce the above copyright
   20       notice, this list of conditions and the following disclaimer in
   21       the documentation and/or other materials provided with the
   22       distribution.
   23
   24    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   25    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   26    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   27    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   28    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   29    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   30    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   31    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   32    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   33    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   34    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   35    POSSIBILITY OF SUCH DAMAGE.
   36*/
   37
   38:- module(json,
   39          [ json_read/2,                % +Stream, -JSONTerm
   40            json_read/3,                % +Stream, -JSONTerm, +Options
   41            atom_json_term/3,           % ?Atom, ?JSONTerm, +Options
   42            json_write/2,               % +Stream, +Term
   43            json_write/3,               % +Stream, +Term, +Options
   44            is_json_term/1,             % @Term
   45            is_json_term/2,             % @Term, +Options
   46                                        % Version 7 dict support
   47            json_read_dict/2,           % +Stream, -Dict
   48            json_read_dict/3,           % +Stream, -Dict, +Options
   49            json_write_dict/2,          % +Stream, +Dict
   50            json_write_dict/3,          % +Stream, +Dict, +Options
   51            atom_json_dict/3            % ?Atom, ?JSONDict, +Options
   52          ]).   53:- use_module(library(record)).   54:- use_module(library(error)).   55:- use_module(library(option)).   56:- use_module(library(lists)).   57
   58:- use_foreign_library(foreign(json)).   59
   60:- multifile
   61    json_write_hook/4,                  % +Term, +Stream, +State, +Options
   62    json_dict_pairs/2.                  % +Dict, -Pairs
   63
   64:- predicate_options(json_read/3, 3,
   65                     [ null(ground),
   66                       true(ground),
   67                       false(ground),
   68                       value_string_as(oneof([atom,string]))
   69                     ]).   70:- predicate_options(json_write/3, 3,
   71                     [ indent(nonneg),
   72                       step(positive_integer),
   73                       tab(positive_integer),
   74                       width(nonneg),
   75                       null(ground),
   76                       true(ground),
   77                       false(ground),
   78                       serialize_unknown(boolean)
   79                     ]).   80:- predicate_options(json_read_dict/3, 3,
   81                     [ tag(atom),
   82                       default_tag(atom),
   83                       pass_to(json_read/3, 3)
   84                     ]).   85:- predicate_options(json_write_dict/3, 3,
   86                     [ tag(atom),
   87                       pass_to(json_write/3, 3)
   88                     ]).   89:- predicate_options(is_json_term/2, 2,
   90                     [ null(ground),
   91                       true(ground),
   92                       false(ground)
   93                     ]).   94:- predicate_options(atom_json_term/3, 3,
   95                     [ as(oneof([atom,string,codes])),
   96                       pass_to(json_read/3, 3),
   97                       pass_to(json_write/3, 3)
   98                     ]).   99
  100/** <module> Reading and writing JSON serialization
  101
  102This module supports reading and  writing   JSON  objects.  This library
  103supports two Prolog representations (the   _new_  representation is only
  104supported in SWI-Prolog version 7 and later):
  105
  106  - The *classical* representation is provided by json_read/3 and
  107    json_write/3.  This represents a JSON object as json(NameValueList),
  108    a JSON string as an atom and the JSON constants =null=, =true= and
  109    =false= as @(null), @(true) and @false.
  110
  111  - The *new* representation is provided by json_read_dict/3 and
  112    json_write_dict/3. This represents a JSON object as a dict, a JSON
  113    string as a Prolog string and the JSON constants using the Prolog
  114    atoms =null=, =true= and =false=.
  115
  116@author Jan Wielemaker
  117@see    http_json.pl links JSON to the HTTP client and server modules.
  118@see    json_convert.pl converts JSON Prolog terms to more comfortable
  119terms.
  120*/
  121
  122:- record json_options(
  123              null:ground = @(null),
  124              true:ground = @(true),
  125              false:ground = @(false),
  126              end_of_file:ground = error,
  127              value_string_as:oneof([atom,string]) = atom,
  128              tag:atom = '',
  129              default_tag:atom).  130
  131default_json_dict_options(
  132    json_options(null, true, false, error, string, '', _)).
  133
  134
  135                 /*******************************
  136                 *       MAP TO/FROM TEXT       *
  137                 *******************************/
  138
  139%!  atom_json_term(?Atom, ?JSONTerm, +Options) is det.
  140%
  141%   Convert between textual  representation  and   a  JSON  term. In
  142%   _write_ mode (JSONTerm to Atom), the option
  143%
  144%       * as(Type)
  145%       defines the output type, which is one of `atom` (default),
  146%       `string`, `codes` or `chars`.
  147
  148atom_json_term(Atom, Term, Options) :-
  149    ground(Atom),
  150    !,
  151    setup_call_cleanup(
  152        open_string(Atom, In),
  153        json_read(In, Term, Options),
  154        close(In)).
  155atom_json_term(Result, Term, Options) :-
  156    select_option(as(Type), Options, Options1, atom),
  157    (   type_term(Type, Result, Out)
  158    ->  true
  159    ;   must_be(oneof([atom,string,codes,chars]), Type)
  160    ),
  161    with_output_to(Out,
  162                   json_write(current_output, Term, Options1)).
  163
  164type_term(atom,   Result, atom(Result)).
  165type_term(string, Result, string(Result)).
  166type_term(codes,  Result, codes(Result)).
  167type_term(chars,  Result, chars(Result)).
  168
  169
  170                 /*******************************
  171                 *           READING            *
  172                 *******************************/
  173
  174%!  json_read(+Stream, -Term) is det.
  175%!  json_read(+Stream, -Term, +Options) is det.
  176%
  177%   Read next JSON value from Stream into a Prolog term. The
  178%   canonical representation for Term is:
  179%
  180%     * A JSON object is mapped to a term json(NameValueList), where
  181%       NameValueList is a list of Name=Value. Name is an atom
  182%       created from the JSON string.
  183%
  184%     * A JSON array is mapped to a Prolog list of JSON values.
  185%
  186%     * A JSON string is mapped to a Prolog atom
  187%
  188%     * A JSON number is mapped to a Prolog number
  189%
  190%     * The JSON constants =true= and =false= are mapped -like JPL-
  191%       to @(true) and @(false).
  192%
  193%     * The JSON constant =null= is mapped to the Prolog term
  194%       @(null)
  195%
  196%   Here is a complete example in  JSON and its corresponding Prolog
  197%   term.
  198%
  199%     ```
  200%     { "name":"Demo term",
  201%       "created": {
  202%         "day":null,
  203%         "month":"December",
  204%         "year":2007
  205%       },
  206%       "confirmed":true,
  207%       "members":[1,2,3]
  208%     }
  209%     ```
  210%
  211%     ```
  212%     json([ name='Demo term',
  213%            created=json([day= @null, month='December', year=2007]),
  214%            confirmed= @true,
  215%            members=[1, 2, 3]
  216%          ])
  217%     ```
  218%
  219%   The following options are processed:
  220%
  221%     - null(+NullTerm)
  222%       Term used to represent JSON =null=.  Default @(null)
  223%     - true(+TrueTerm)
  224%       Term used to represent JSON =true=.  Default @(true)
  225%     - false(+FalseTerm)
  226%       Term used to represent JSON =false=.  Default @(false)
  227%     - end_of_file(+ErrorOrTerm)
  228%       If end of file is reached after skipping white space
  229%       but before any input is processed take the following
  230%       action (default `error`):
  231%         - If ErrorOrTerm == `error`, throw an unexpected
  232%           end of file syntax error
  233%         - Otherwise return ErrorOrTerm.
  234%       Returning an status term is required to process
  235%       [Concatenated
  236%       JSON](https://en.wikipedia.org/wiki/JSON_streaming#Concatenated_JSON).
  237%       Suggested values are `@(eof)` or `end_of_file`.
  238%     - value_string_as(+Type)
  239%       Prolog type used for strings used as value. Default is `atom`.
  240%       The alternative is `string`, producing a packed string object.
  241%       Please note that `codes` or `chars` would produce ambiguous
  242%       output and are therefore not supported.
  243%
  244%   @see    json_read_dict/3 to read a JSON term using the version 7
  245%           extended data types.
  246
  247json_read(Stream, Term) :-
  248    default_json_options(Options),
  249    (   json_value_top(Stream, Term, Options)
  250    ->  true
  251    ;   syntax_error(illegal_json, Stream)
  252    ).
  253json_read(Stream, Term, Options) :-
  254    make_json_options(Options, OptionTerm, _RestOptions),
  255    (   json_value_top(Stream, Term, OptionTerm)
  256    ->  true
  257    ;   syntax_error(illegal_json, Stream)
  258    ).
  259
  260json_value_top(Stream, Term, Options) :-
  261    stream_property(Stream, type(binary)),
  262    !,
  263    setup_call_cleanup(
  264        set_stream(Stream, encoding(utf8)),
  265        json_value_top_(Stream, Term, Options),
  266        set_stream(Stream, type(binary))).
  267json_value_top(Stream, Term, Options) :-
  268    json_value_top_(Stream, Term, Options).
  269
  270json_value_top_(Stream, Term, Options) :-
  271    get_code(Stream, C0),
  272    ws(C0, Stream, C1),
  273    (   C1 == -1
  274    ->  json_options_end_of_file(Options, Action),
  275        (   Action == error
  276        ->  syntax_error(unexpected_end_of_file, Stream)
  277        ;   Term = Action
  278        )
  279    ;   json_term_top(C1, Stream, Term, Options)
  280    ).
  281
  282json_value(Stream, Term, Next, Options) :-
  283    get_code(Stream, C0),
  284    ws(C0, Stream, C1),
  285    (   C1 == -1
  286    ->  syntax_error(unexpected_end_of_file, Stream)
  287    ;   json_term(C1, Stream, Term, Next, Options)
  288    ).
  289
  290json_term(C0, Stream, JSON, Next, Options) :-
  291    json_term_top(C0, Stream, JSON, Options),
  292    get_code(Stream, Next).
  293
  294json_term_top(0'{, Stream, json(Pairs), Options) :-
  295    !,
  296    ws(Stream, C),
  297    json_pairs(C, Stream, Pairs, Options).
  298json_term_top(0'[, Stream, Array, Options) :-
  299    !,
  300    ws(Stream, C),
  301    json_array(C, Stream, Array, Options).
  302json_term_top(0'", Stream, String, Options) :-
  303    !,
  304    get_code(Stream, C1),
  305    json_string_codes(C1, Stream, Codes),
  306    json_options_value_string_as(Options, Type),
  307    codes_to_type(Type, Codes, String).
  308json_term_top(0'-, Stream, Number, _Options) :-
  309    !,
  310    json_read_number(Stream, 0'-, Number).
  311json_term_top(D, Stream, Number, _Options) :-
  312    between(0'0, 0'9, D),
  313    !,
  314    json_read_number(Stream, D, Number).
  315json_term_top(C, Stream, Constant, Options) :-
  316    json_read_constant(C, Stream, ID),
  317    json_constant(ID, Constant, Options).
  318
  319json_pairs(0'}, _, [], _) :- !.
  320json_pairs(C0, Stream, [Pair|Tail], Options) :-
  321    json_pair(C0, Stream, Pair, C, Options),
  322    ws(C, Stream, Next),
  323    (   Next == 0',
  324    ->  ws(Stream, C2),
  325        json_pairs(C2, Stream, Tail, Options)
  326    ;   Next == 0'}
  327    ->  Tail = []
  328    ;   syntax_error(illegal_object, Stream)
  329    ).
  330
  331json_pair(C0, Stream, Name=Value, Next, Options) :-
  332    json_string_as_atom(C0, Stream, Name),
  333    ws(Stream, C),
  334    C == 0':,
  335    json_value(Stream, Value, Next, Options).
  336
  337
  338json_array(0'], _, [], _) :- !.
  339json_array(C0, Stream, [Value|Tail], Options) :-
  340    json_term(C0, Stream, Value, C, Options),
  341    ws(C, Stream, Next),
  342    (   Next == 0',
  343    ->  ws(Stream, C1),
  344        json_array(C1, Stream, Tail, Options)
  345    ;   Next == 0']
  346    ->  Tail = []
  347    ;   syntax_error(illegal_array, Stream)
  348    ).
  349
  350codes_to_type(atom, Codes, Atom) :-
  351    atom_codes(Atom, Codes).
  352codes_to_type(string, Codes, Atom) :-
  353    string_codes(Atom, Codes).
  354codes_to_type(codes, Codes, Codes).
  355
  356json_string_as_atom(0'", Stream, Atom) :-
  357    get_code(Stream, C1),
  358    json_string_codes(C1, Stream, Codes),
  359    atom_codes(Atom, Codes).
  360
  361json_string_codes(0'", _, []) :- !.
  362json_string_codes(0'\\, Stream, [H|T]) :-
  363    !,
  364    get_code(Stream, C0),
  365    (   escape(C0, Stream, H)
  366    ->  true
  367    ;   syntax_error(illegal_string_escape, Stream)
  368    ),
  369    get_code(Stream, C1),
  370    json_string_codes(C1, Stream, T).
  371json_string_codes(-1, Stream, _) :-
  372    !,
  373    syntax_error(eof_in_string, Stream).
  374json_string_codes(C, Stream, [C|T]) :-
  375    get_code(Stream, C1),
  376    json_string_codes(C1, Stream, T).
  377
  378escape(0'", _, 0'") :- !.
  379escape(0'\\, _, 0'\\) :- !.
  380escape(0'/, _, 0'/) :- !.
  381escape(0'b, _, 0'\b) :- !.
  382escape(0'f, _, 0'\f) :- !.
  383escape(0'n, _, 0'\n) :- !.
  384escape(0'r, _, 0'\r) :- !.
  385escape(0't, _, 0'\t) :- !.
  386escape(0'u, Stream, C) :-
  387    get_XXXX(Stream, H),
  388    (   hi_surrogate(H)
  389    ->  get_surrogate_tail(Stream, H, C)
  390    ;   C = H
  391    ).
  392
  393get_XXXX(Stream, C) :-
  394    get_xdigit(Stream, D1),
  395    get_xdigit(Stream, D2),
  396    get_xdigit(Stream, D3),
  397    get_xdigit(Stream, D4),
  398    C is D1<<12+D2<<8+D3<<4+D4.
  399
  400get_xdigit(Stream, D) :-
  401    get_code(Stream, C),
  402    code_type(C, xdigit(D)),
  403    !.
  404get_xdigit(Stream, _) :-
  405    syntax_error(hexdigit_expected, Stream).
  406
  407get_surrogate_tail(Stream, Hi, Codepoint) :-
  408    (   get_code(Stream, 0'\\),
  409        get_code(Stream, 0'u),
  410        get_XXXX(Stream, Lo),
  411        surrogate([Hi, Lo], Codepoint)
  412    ->  true
  413    ;   syntax_error(illegal_surrogate_pair, Stream)
  414    ).
  415
  416
  417hi_surrogate(C) :-
  418    C >= 0xD800, C < 0xDC00.
  419
  420lo_surrogate(C) :-
  421    C >= 0xDC00, C < 0xE000.
  422
  423surrogate([Hi, Lo], Codepoint) :-
  424    hi_surrogate(Hi),
  425    lo_surrogate(Lo),
  426    Codepoint is (Hi - 0xD800) * 0x400 + (Lo - 0xDC00) + 0x10000.
  427
  428json_read_constant(0't, Stream, true) :-
  429    !,
  430    must_see(`rue`, Stream, true).
  431json_read_constant(0'f, Stream, false) :-
  432    !,
  433    must_see(`alse`, Stream, false).
  434json_read_constant(0'n, Stream, null) :-
  435    !,
  436    must_see(`ull`, Stream, null).
  437
  438must_see([], _Stream, _).
  439must_see([H|T], Stream, Name) :-
  440    get_code(Stream, C),
  441    (   C == H
  442    ->  true
  443    ;   syntax_error(json_expected(Name), Stream)
  444    ),
  445    must_see(T, Stream, Name).
  446
  447json_constant(true, Constant, Options) :-
  448    !,
  449    json_options_true(Options, Constant).
  450json_constant(false, Constant, Options) :-
  451    !,
  452    json_options_false(Options, Constant).
  453json_constant(null, Constant, Options) :-
  454    !,
  455    json_options_null(Options, Constant).
  456
  457%!  ws(+Stream, -Next) is det.
  458%!  ws(+C0, +Stream, -Next)
  459%
  460%   Skip white space on the Stream, returning the first non-ws
  461%   character.  Also skips =|//|= ... comments.
  462
  463ws(Stream, Next) :-
  464    get_code(Stream, C0),
  465    json_skip_ws(Stream, C0, Next).
  466
  467ws(C0, Stream, Next) :-
  468    json_skip_ws(Stream, C0, Next).
  469
  470syntax_error(Message, Stream) :-
  471    stream_error_context(Stream, Context),
  472    throw(error(syntax_error(json(Message)), Context)).
  473
  474stream_error_context(Stream, stream(Stream, Line, LinePos, CharNo)) :-
  475    stream_pair(Stream, Read, _),
  476    character_count(Read, CharNo),
  477    line_position(Read, LinePos),
  478    line_count(Read, Line).
  479
  480
  481                 /*******************************
  482                 *          JSON OUTPUT         *
  483                 *******************************/
  484
  485%!  json_write_string(+Stream, +Text) is det.
  486%
  487%   Write a JSON string to  Stream.  Stream   must  be  opened  in a
  488%   Unicode capable encoding, typically UTF-8.
  489
  490% foreign json_write_string/2.
  491
  492%!  json_write_indent(+Stream, +Indent, +TabDistance) is det.
  493%
  494%   Newline and indent to  Indent.  A   Newline  is  only written if
  495%   line_position(Stream, Pos) is not 0. Then   it  writes Indent //
  496%   TabDistance tab characters and Indent mode TabDistance spaces.
  497
  498% foreign json_write_indent/3.
  499
  500%!  json_write(+Stream, +Term) is det.
  501%!  json_write(+Stream, +Term, +Options) is det.
  502%
  503%   Write a JSON term to Stream. The JSON   object is of the same format
  504%   as  produced  by  json_read/2,  though  we    allow  for  some  more
  505%   flexibility with regard to pairs  in   objects.  All  of Name=Value,
  506%   Name-Value and Name(Value) produce the same output.
  507%
  508%   Values can be of  the  form  #(Term),   which  causes  `Term`  to be
  509%   _stringified_ if it is not  an   atom  or string. Stringification is
  510%   based on term_string/2.
  511%
  512%   Rational numbers are emitted as  floating   point  numbers. The hook
  513%   json_write_hook/4  can  be  used   to    realize   domain   specific
  514%   alternatives.
  515%
  516%   The version 7 _dict_ type is supported   as well. Optionally, if the
  517%   dict has a _tag_, a  property  "type":"tag"   can  be  added  to the
  518%   object. This behaviour can be controlled using the =tag= option (see
  519%   below). For example:
  520%
  521%     ==
  522%     ?- json_write(current_output, point{x:1,y:2}).
  523%     {
  524%       "x":1,
  525%       "y":2
  526%     }
  527%     ==
  528%
  529%     ==
  530%     ?- json_write(current_output, point{x:1,y:2}, [tag(type)]).
  531%     {
  532%       "type":"point",
  533%       "x":1,
  534%       "y":2
  535%     }
  536%     ==
  537%
  538%   In addition to the options recognised by json_read/3, we process
  539%   the following options are recognised:
  540%
  541%       * width(+Width)
  542%       Width in which we try to format the result.  Too long lines
  543%       switch from _horizontal_ to _vertical_ layout for better
  544%       readability. If performance is critical and human
  545%       readability is not an issue use Width = 0, which causes a
  546%       single-line output.
  547%
  548%       * step(+Step)
  549%       Indentation increnment for next level.  Default is 2.
  550%
  551%       * tab(+TabDistance)
  552%       Distance between tab-stops.  If equal to Step, layout
  553%       is generated with one tab per level.
  554%
  555%       * serialize_unknown(+Boolean)
  556%       If =true= (default =false=), serialize unknown terms and
  557%       print them as a JSON string.  The default raises a type
  558%       error.  Note that this option only makes sense if you can
  559%       guarantee that the passed value is not an otherwise valid
  560%       Prolog reporesentation of a Prolog term.
  561%
  562%   If a string is  emitted,  the   sequence  =|</|=  is  emitted as
  563%   =|<\/|=. This is valid  JSON  syntax   which  ensures  that JSON
  564%   objects  can  be  safely  embedded  into  an  HTML  =|<script>|=
  565%   element.
  566
  567%!  json_write_hook(+Term, +Stream, +State, +Options) is semidet.
  568%
  569%   Hook that can be used to  emit   a  JSON  representation for Term to
  570%   Stream. If the  predicate  succeeds  it   __must__  have  written  a
  571%   __valid__ JSON data element and if it fails it may not have produced
  572%   any output. This facility may be used  to map arbitrary Prolog terms
  573%   to JSON. It was added to manage   the  precision with which floating
  574%   point numbers are emitted.
  575%
  576%   Note that this hook is shared by all   users  of this library. It is
  577%   generally  adviced  to  map  a  unique    compound   term  to  avoid
  578%   interference with normal output.
  579%
  580%   @arg State and Options are opaque handles to the current output
  581%   state and settings.  Future versions may provide documented access
  582%   to these terms.  Currently it is adviced to ignore these arguments.
  583
  584%!  json_dict_pairs(+Dict, -Pairs) is semidet.
  585%
  586%   This hook may be used to order the   keys of an object. If it fails,
  587%   dict_pairs/3 is used which produces an ordered list of keys.
  588
  589:- record json_write_state(indent:nonneg = 0,
  590                           step:positive_integer = 2,
  591                           tab:positive_integer = 8,
  592                           width:nonneg = 72,
  593                           serialize_unknown:boolean = false
  594                          ).  595
  596json_write(Stream, Term) :-
  597    json_write(Stream, Term, []).
  598json_write(Stream, Term, Options) :-
  599    make_json_write_state(Options, State, Options1),
  600    make_json_options(Options1, OptionTerm, _RestOptions),
  601    json_write_term(Term, Stream, State, OptionTerm).
  602
  603json_write_term(Var, _, _, _) :-
  604    var(Var),
  605    !,
  606    instantiation_error(Var).
  607json_write_term(json(Pairs), Stream, State, Options) :-
  608    !,
  609    json_write_object(Pairs, Stream, State, Options).
  610json_write_term(Dict, Stream, State, Options) :-
  611    is_dict(Dict, Tag),
  612    !,
  613    json_pairs(Dict, Pairs0),
  614    (   nonvar(Tag),
  615        json_options_tag(Options, Name),
  616        Name \== ''
  617    ->  Pairs = [Name-Tag|Pairs0]
  618    ;   Pairs = Pairs0
  619    ),
  620    json_write_object(Pairs, Stream, State, Options).
  621json_write_term(List, Stream, State, Options) :-
  622    is_list(List),
  623    !,
  624    space_if_not_at_left_margin(Stream, State),
  625    write(Stream, '['),
  626    (   json_write_state_width(State, Width),
  627        (   Width == 0
  628        ->  true
  629        ;   json_write_state_indent(State, Indent),
  630            json_print_length(List, Options, Width, Indent, _)
  631        )
  632    ->  set_width_of_json_write_state(0, State, State2),
  633        write_array_hor(List, Stream, State2, Options),
  634        write(Stream, ']')
  635    ;   step_indent(State, State2),
  636        write_array_ver(List, Stream, State2, Options),
  637        indent(Stream, State),
  638        write(Stream, ']')
  639    ).
  640
  641json_write_term(Term, Stream, State, Options) :-
  642    json_write_hook(Term, Stream, State, Options),
  643    !.
  644json_write_term(Number, Stream, _State, _Options) :-
  645    number(Number),
  646    !,
  647    (   float(Number)
  648    ->  write(Stream, Number)
  649    ;   integer(Number)
  650    ->  write(Stream, Number)
  651    ;   Float is float(Number)              % rational number
  652    ->  write(Stream, Float)
  653    ).
  654json_write_term(True, Stream, _State, Options) :-
  655    json_options_true(Options, True),
  656    !,
  657    write(Stream, true).
  658json_write_term(False, Stream, _State, Options) :-
  659    json_options_false(Options, False),
  660    !,
  661    write(Stream, false).
  662json_write_term(Null, Stream, _State, Options) :-
  663    json_options_null(Options, Null),
  664    !,
  665    write(Stream, null).
  666json_write_term(#(Text), Stream, _State, _Options) :-
  667    !,
  668    (   (   atom(Text)
  669        ;   string(Text)
  670        )
  671    ->  json_write_string(Stream, Text)
  672    ;   term_string(Text, String),
  673        json_write_string(Stream, String)
  674    ).
  675json_write_term(String, Stream, _State, _Options) :-
  676    atom(String),
  677    !,
  678    json_write_string(Stream, String).
  679json_write_term(String, Stream, _State, _Options) :-
  680    string(String),
  681    !,
  682    json_write_string(Stream, String).
  683json_write_term(AnyTerm, Stream, State, _Options) :-
  684    (   json_write_state_serialize_unknown(State, true)
  685    ->  term_string(AnyTerm, String),
  686        json_write_string(Stream, String)
  687    ;   type_error(json_term, AnyTerm)
  688    ).
  689
  690json_pairs(Dict, Pairs) :-
  691    json_dict_pairs(Dict, Pairs),
  692    !.
  693json_pairs(Dict, Pairs) :-
  694    dict_pairs(Dict, _, Pairs).
  695
  696json_write_object(Pairs, Stream, State, Options) :-
  697    space_if_not_at_left_margin(Stream, State),
  698    write(Stream, '{'),
  699    (   json_write_state_width(State, Width),
  700        (   Width == 0
  701        ->  true
  702        ;   json_write_state_indent(State, Indent),
  703            json_print_length(json(Pairs), Options, Width, Indent, _)
  704        )
  705    ->  set_width_of_json_write_state(0, State, State2),
  706        write_pairs_hor(Pairs, Stream, State2, Options),
  707        write(Stream, '}')
  708    ;   step_indent(State, State2),
  709        write_pairs_ver(Pairs, Stream, State2, Options),
  710        indent(Stream, State),
  711        write(Stream, '}')
  712    ).
  713
  714
  715write_pairs_hor([], _, _, _).
  716write_pairs_hor([H|T], Stream, State, Options) :-
  717    json_pair(H, Name, Value),
  718    json_write_string(Stream, Name),
  719    write(Stream, ':'),
  720    json_write_term(Value, Stream, State, Options),
  721    (   T == []
  722    ->  true
  723    ;   (   json_write_state_width(State, 0)
  724        ->  write(Stream, ',')
  725        ;   write(Stream, ', ')
  726        ),
  727        write_pairs_hor(T, Stream, State, Options)
  728    ).
  729
  730write_pairs_ver([], _, _, _).
  731write_pairs_ver([H|T], Stream, State, Options) :-
  732    indent(Stream, State),
  733    json_pair(H, Name, Value),
  734    json_write_string(Stream, Name),
  735    write(Stream, ':'),
  736    json_write_term(Value, Stream, State, Options),
  737    (   T == []
  738    ->  true
  739    ;   write(Stream, ','),
  740        write_pairs_ver(T, Stream, State, Options)
  741    ).
  742
  743
  744json_pair(Var, _, _) :-
  745    var(Var),
  746    !,
  747    instantiation_error(Var).
  748json_pair(Name=Value, Name, Value) :- !.
  749json_pair(Name-Value, Name, Value) :- !.
  750json_pair(NameValue, Name, Value) :-
  751    compound(NameValue),
  752    NameValue =.. [Name, Value],
  753    !.
  754json_pair(Pair, _, _) :-
  755    type_error(json_pair, Pair).
  756
  757
  758write_array_hor([], _, _, _).
  759write_array_hor([H|T], Stream, State, Options) :-
  760    json_write_term(H, Stream, State, Options),
  761    (   T == []
  762    ->  write(Stream, ' ')
  763    ;   write(Stream, ', '),
  764        write_array_hor(T, Stream, State, Options)
  765    ).
  766
  767write_array_ver([], _, _, _).
  768write_array_ver([H|T], Stream, State, Options) :-
  769    indent(Stream, State),
  770    json_write_term(H, Stream, State, Options),
  771    (   T == []
  772    ->  true
  773    ;   write(Stream, ','),
  774        write_array_ver(T, Stream, State, Options)
  775    ).
  776
  777
  778indent(Stream, State) :-
  779    json_write_state_indent(State, Indent),
  780    json_write_state_tab(State, Tab),
  781    json_write_indent(Stream, Indent, Tab).
  782
  783step_indent(State0, State) :-
  784    json_write_state_indent(State0, Indent),
  785    json_write_state_step(State0, Step),
  786    NewIndent is Indent+Step,
  787    set_indent_of_json_write_state(NewIndent, State0, State).
  788
  789space_if_not_at_left_margin(Stream, State) :-
  790    stream_pair(Stream, _, Write),
  791    line_position(Write, LinePos),
  792    (   LinePos == 0
  793    ;   json_write_state_indent(State, LinePos)
  794    ),
  795    !.
  796space_if_not_at_left_margin(Stream, _) :-
  797    put_char(Stream, ' ').
  798
  799
  800%!  json_print_length(+Value, +Options, +Max, +Len0, +Len) is semidet.
  801%
  802%   True if Len-Len0 is the print-length of Value on a single line
  803%   and Len-Len0 =< Max.
  804%
  805%   @tbd    Escape sequences in strings are not considered.
  806
  807json_print_length(Var, _, _, _, _) :-
  808    var(Var),
  809    !,
  810    instantiation_error(Var).
  811json_print_length(json(Pairs), Options, Max, Len0, Len) :-
  812    !,
  813    Len1 is Len0 + 2,
  814    Len1 =< Max,
  815    must_be(list, Pairs),
  816    pairs_print_length(Pairs, Options, Max, Len1, Len).
  817json_print_length(Dict, Options, Max, Len0, Len) :-
  818    is_dict(Dict),
  819    !,
  820    dict_pairs(Dict, _Tag, Pairs),
  821    Len1 is Len0 + 2,
  822    Len1 =< Max,
  823    pairs_print_length(Pairs, Options, Max, Len1, Len).
  824json_print_length(Array, Options, Max, Len0, Len) :-
  825    is_list(Array),
  826    !,
  827    Len1 is Len0 + 2,
  828    Len1 =< Max,
  829    array_print_length(Array, Options, Max, Len1, Len).
  830json_print_length(Null, Options, Max, Len0, Len) :-
  831    json_options_null(Options, Null),
  832    !,
  833    Len is Len0 + 4,
  834    Len =< Max.
  835json_print_length(False, Options, Max, Len0, Len) :-
  836    json_options_false(Options, False),
  837    !,
  838    Len is Len0 + 5,
  839    Len =< Max.
  840json_print_length(True, Options, Max, Len0, Len) :-
  841    json_options_true(Options, True),
  842    !,
  843    Len is Len0 + 4,
  844    Len =< Max.
  845json_print_length(Number, _Options, Max, Len0, Len) :-
  846    number(Number),
  847    !,
  848    write_length(Number, AL, []),
  849    Len is Len0 + AL,
  850    Len =< Max.
  851json_print_length(@(Id), _Options, Max, Len0, Len) :-
  852    atom(Id),
  853    !,
  854    atom_length(Id, IdLen),
  855    Len is Len0+IdLen,
  856    Len =< Max.
  857json_print_length(String, _Options, Max, Len0, Len) :-
  858    string_len(String, Len0, Len),
  859    !,
  860    Len =< Max.
  861json_print_length(AnyTerm, _Options, Max, Len0, Len) :-
  862    write_length(AnyTerm, AL, []),          % will be serialized
  863    Len is Len0 + AL+2,
  864    Len =< Max.
  865
  866pairs_print_length([], _, _, Len, Len).
  867pairs_print_length([H|T], Options, Max, Len0, Len) :-
  868    pair_len(H, Options, Max, Len0, Len1),
  869    (   T == []
  870    ->  Len = Len1
  871    ;   Len2 is Len1 + 2,
  872        Len2 =< Max,
  873        pairs_print_length(T, Options, Max, Len2, Len)
  874    ).
  875
  876pair_len(Pair, Options, Max, Len0, Len) :-
  877    compound(Pair),
  878    pair_nv(Pair, Name, Value),
  879    !,
  880    string_len(Name, Len0, Len1),
  881    Len2 is Len1+2,
  882    Len2 =< Max,
  883    json_print_length(Value, Options, Max, Len2, Len).
  884pair_len(Pair, _Options, _Max, _Len0, _Len) :-
  885    type_error(pair, Pair).
  886
  887pair_nv(Name=Value, Name, Value) :- !.
  888pair_nv(Name-Value, Name, Value) :- !.
  889pair_nv(Term, Name, Value) :-
  890    compound_name_arguments(Term, Name, [Value]).
  891
  892array_print_length([], _, _, Len, Len).
  893array_print_length([H|T], Options, Max, Len0, Len) :-
  894    json_print_length(H, Options, Max, Len0, Len1),
  895    (   T == []
  896    ->  Len = Len1
  897    ;   Len2 is Len1+2,
  898        Len2 =< Max,
  899        array_print_length(T, Options, Max, Len2, Len)
  900    ).
  901
  902string_len(String, Len0, Len) :-
  903    atom(String),
  904    !,
  905    atom_length(String, AL),
  906    Len is Len0 + AL + 2.
  907string_len(String, Len0, Len) :-
  908    string(String),
  909    !,
  910    string_length(String, AL),
  911    Len is Len0 + AL + 2.
  912
  913
  914                 /*******************************
  915                 *             TEST             *
  916                 *******************************/
  917
  918%!  is_json_term(@Term) is semidet.
  919%!  is_json_term(@Term, +Options) is semidet.
  920%
  921%   True if Term is  a  json  term.   Options  are  the  same as for
  922%   json_read/2, defining the Prolog  representation   for  the JSON
  923%   =true=, =false= and =null= constants.
  924
  925is_json_term(Term) :-
  926    default_json_options(Options),
  927    is_json_term2(Options, Term).
  928
  929is_json_term(Term, Options) :-
  930    make_json_options(Options, OptionTerm, _RestOptions),
  931    is_json_term2(OptionTerm, Term).
  932
  933is_json_term2(_, Var) :-
  934    var(Var), !, fail.
  935is_json_term2(Options, json(Pairs)) :-
  936    !,
  937    is_list(Pairs),
  938    maplist(is_json_pair(Options), Pairs).
  939is_json_term2(Options, List) :-
  940    is_list(List),
  941    !,
  942    maplist(is_json_term2(Options), List).
  943is_json_term2(_, Primitive) :-
  944    atomic(Primitive),
  945    !.           % atom, string or number
  946is_json_term2(Options, True) :-
  947    json_options_true(Options, True).
  948is_json_term2(Options, False) :-
  949    json_options_false(Options, False).
  950is_json_term2(Options, Null) :-
  951    json_options_null(Options, Null).
  952
  953is_json_pair(_, Var) :-
  954    var(Var), !, fail.
  955is_json_pair(Options, Name=Value) :-
  956    atom(Name),
  957    is_json_term2(Options, Value).
  958
  959                 /*******************************
  960                 *         DICT SUPPORT         *
  961                 *******************************/
  962
  963%!  json_read_dict(+Stream, -Dict) is det.
  964%!  json_read_dict(+Stream, -Dict, +Options) is det.
  965%
  966%   Read  a  JSON  object,  returning  objects    as  a  dicts.  The
  967%   representation depends on the options, where the default is:
  968%
  969%     * String values are mapped to Prolog strings
  970%     * JSON =true=, =false= and =null= are represented using these
  971%       Prolog atoms.
  972%     * JSON objects are mapped to dicts.
  973%     * Optionally, a =type= field in an object assigns a tag for
  974%       the dict.
  975%
  976%   The predicate json_read_dict/3 processes  the   same  options as
  977%   json_read/3,  but  with  different  defaults.  In  addition,  it
  978%   processes the `tag` option. See   json_read/3  for details about
  979%   the shared options.
  980%
  981%     * tag(+Name)
  982%       When converting to/from a dict, map the indicated JSON
  983%       attribute to the dict _tag_. No mapping is performed if Name
  984%       is the empty atom ('', default). See json_read_dict/2 and
  985%       json_write_dict/2.
  986%     * default_tag(+Tag)
  987%       Provide the default tag if the above `tag` option does not
  988%       apply.
  989%     * null(+NullTerm)
  990%       Default the atom `null`.
  991%     * true(+TrueTerm)
  992%       Default the atom `true`.
  993%     * false(+FalseTerm)
  994%       Default the atom `false`
  995%     * end_of_file(+ErrorOrTerm)
  996%       Action on reading end-of-file. See json_read/3 for details.
  997%     * value_string_as(+Type)
  998%       Prolog type used for strings used as value.  Default
  999%       is `string`.  The alternative is `atom`, producing a
 1000%       packed string object.
 1001
 1002json_read_dict(Stream, Dict) :-
 1003    json_read_dict(Stream, Dict, []).
 1004
 1005json_read_dict(Stream, Dict, Options) :-
 1006    make_json_dict_options(Options, OptionTerm, _RestOptions),
 1007    (   json_value_top(Stream, Term, OptionTerm)
 1008    ->  true
 1009    ;   syntax_error(illegal_json, Stream)
 1010    ),
 1011    term_to_dict(Term, Dict, OptionTerm).
 1012
 1013term_to_dict(json(Pairs), Dict, Options) :-
 1014    !,
 1015    (   json_options_tag(Options, TagName),
 1016        Tag \== '',
 1017        select(TagName = Tag0, Pairs, NVPairs),
 1018        to_atom(Tag0, Tag)
 1019    ->  json_dict_pairs(NVPairs, DictPairs, Options)
 1020    ;   json_options_default_tag(Options, DefTag),
 1021        (   var(DefTag)
 1022        ->  true
 1023        ;   Tag = DefTag
 1024        ),
 1025        json_dict_pairs(Pairs, DictPairs, Options)
 1026    ),
 1027    dict_create(Dict, Tag, DictPairs).
 1028term_to_dict(Value0, Value, _Options) :-
 1029    atomic(Value0), Value0 \== [],
 1030    !,
 1031    Value = Value0.
 1032term_to_dict(List0, List, Options) :-
 1033    is_list(List0),
 1034    !,
 1035    terms_to_dicts(List0, List, Options).
 1036term_to_dict(Special, Special, Options) :-
 1037    (   json_options_true(Options, Special)
 1038    ;   json_options_false(Options, Special)
 1039    ;   json_options_null(Options, Special)
 1040    ;   json_options_end_of_file(Options, Special)
 1041    ),
 1042    !.
 1043
 1044json_dict_pairs([], [], _).
 1045json_dict_pairs([Name=Value0|T0], [Name=Value|T], Options) :-
 1046    term_to_dict(Value0, Value, Options),
 1047    json_dict_pairs(T0, T, Options).
 1048
 1049terms_to_dicts([], [], _).
 1050terms_to_dicts([Value0|T0], [Value|T], Options) :-
 1051    term_to_dict(Value0, Value, Options),
 1052    terms_to_dicts(T0, T, Options).
 1053
 1054to_atom(Tag, Atom) :-
 1055    string(Tag),
 1056    !,
 1057    atom_string(Atom, Tag).
 1058to_atom(Atom, Atom) :-
 1059    atom(Atom).
 1060
 1061%!  json_write_dict(+Stream, +Dict) is det.
 1062%!  json_write_dict(+Stream, +Dict, +Options) is det.
 1063%
 1064%   Write a JSON term, represented using dicts.  This is the same as
 1065%   json_write/3, but assuming the default   representation  of JSON
 1066%   objects as dicts.
 1067
 1068json_write_dict(Stream, Dict) :-
 1069    json_write_dict(Stream, Dict, []).
 1070
 1071json_write_dict(Stream, Dict, Options) :-
 1072    make_json_write_state(Options, State, Options1),
 1073    make_json_dict_options(Options1, OptionTerm, _RestOptions),
 1074    json_write_term(Dict, Stream, State, OptionTerm).
 1075
 1076
 1077make_json_dict_options(Options, Record, RestOptions) :-
 1078    default_json_dict_options(Record0),
 1079    set_json_options_fields(Options, Record0, Record, RestOptions).
 1080
 1081%!  atom_json_dict(+Atom, -JSONDict, +Options) is det.
 1082%!  atom_json_dict(-Text, +JSONDict, +Options) is det.
 1083%
 1084%   Convert  between  textual  representation  and    a   JSON  term
 1085%   represented as a dict. Options are as for json_read/3.
 1086%   In _write_ mode, the addtional option
 1087%
 1088%       * as(Type)
 1089%       defines the output type, which is one of =atom=,
 1090%       =string= or =codes=.
 1091
 1092atom_json_dict(Atom, Term, Options) :-
 1093    ground(Atom),
 1094    !,
 1095    setup_call_cleanup(
 1096        open_string(Atom, In),
 1097        json_read_dict(In, Term, Options),
 1098        close(In)).
 1099atom_json_dict(Result, Term, Options) :-
 1100    select_option(as(Type), Options, Options1, atom),
 1101    (   type_term(Type, Result, Out)
 1102    ->  true
 1103    ;   must_be(oneof([atom,string,codes]), Type)
 1104    ),
 1105    with_output_to(Out,
 1106                   json_write_dict(current_output, Term, Options1)).
 1107
 1108
 1109                 /*******************************
 1110                 *           MESSAGES           *
 1111                 *******************************/
 1112
 1113:- multifile
 1114    prolog:error_message/3. 1115
 1116prolog:error_message(syntax_error(json(Id))) -->
 1117    [ 'JSON syntax error: ' ],
 1118    json_syntax_error(Id).
 1119
 1120json_syntax_error(illegal_comment) -->
 1121    [ 'Illegal comment' ].
 1122json_syntax_error(illegal_string_escape) -->
 1123    [ 'Illegal escape sequence in string' ].
 1124json_syntax_error(illegal_surrogate_pair) -->
 1125    [ 'Illegal escaped surrogate pair in string' ]