# `json`

The `json` library provides predicates for parsing and generating data in the JSON format based on the specification and standard found at:

- https://www.rfc-editor.org/rfc/rfc8259

- https://www.ecma-international.org/publications-and-standards/standards/ecma-404/

It includes parametric objects whose parameters allow selecting the representation for parsed JSON objects (`curly` or `list`), JSON text strings (`atom`, `chars`, or `codes`) and JSON pairs (`dash`, `equal`, or `colon`).

## API documentation

Open the ../../docs/library_index.html#json link in a web browser.

## Loading

To load all entities in this library, load the `loader.lgt` file:

    | ?- logtalk_load(json(loader)).

## Testing

To test this library predicates, load the `tester.lgt` file:

    | ?- logtalk_load(json(tester)).

Some of the sample JSON test files are based on examples published at:

https://www.json.org/

## Representation

The following choices of syntax has been made to represent JSON elements as terms:

- By default, JSON objects are represented using curly-bracketed terms, `{Pairs}`, where each pair uses the representation `Key-Value` (see below for alternative representations).

- Arrays are represented using lists.

- Text strings can be represented as atoms, `chars(List)`, or `codes(List)`. The default when decoding is to use atoms when using the `json` object. To decode text strings into lists of chars or codes, use the `json/1` with the parameter bound to `chars` or `codes`. For example:

      | ?- json::parse(codes([34,104,101,108,108,111,34]), Term).
      Term = hello
      yes

      | ?- json(atom)::parse(codes([34,104,101,108,108,111,34]), Term).
      Term = hello
      yes

      | ?- json(chars)::parse(codes([34,104,101,108,108,111,34]), Term).
      Term = chars([h,e,l,l,o])
      yes

      | ?- json(codes)::parse(codes([34,104,101,108,108,111,34]), Term).
      Term = codes([104,101,108,108,111])
      yes

- The JSON values `false`, `true` and `null` are represented by, respectively, the `@false`, `@true` and `@null` compound terms.

The following table exemplifies the term equivalents of JSON elements using default representations for objects, pairs, and strings:

JSON | term
--- | ---
[1,2] | [1,2]
true | @true
false | @false
null | @null
-1 | -1
[1.2345] | [1.2345]
[] | []
[2147483647] | [2147483647]
[0] | [0]
[1234567890123456789] | [1234567890123456789]
[false] | [@false]
[-2147483648] | [-2147483648]
{“a”:null,”foo”:”bar”} | {a-@null, foo-bar}
[2.225073858507201e-308] | [2.225073858507201e-308]
[0,1] | [0,1]
[2.2250738585072014e-308] | [2.2250738585072014e-308]
[1.7976931348623157e+308] | [1.7976931348623157e+308]
[0.0] | [0.0]
[4294967295] | [4294967295]
[-1234567890123456789] | [-1234567890123456789]
[“foo”] | [foo]
[1] | [1]
[null] | [@null]
[-1.2345] | [-1.2345]
[5.0e-324] | [5.0e-324]
[-1] | [-1]
[true] | [@true]
[9223372036854775807] | [9223372036854775807]

For JSON objects that are two possible term representations:

JSON object | term (curly)
--- | ---
{“a”:1, “b”:2, “c”:3} | {a-1, b-2, c-3}
{} | {}

and:

JSON object | term (list)
--- | ---
{“a”:1, “b”:2, “c”:3} | json([a-1, b-2, c-3])
{} | json([])

For JSON pairs that are three possible representations:

JSON object | term (dash)
--- | ---
{“a”:1, “b”:2, “c”:3} | {a-1, b-2, c-3}

and:

JSON object | term (equal)
--- | ---
{“a”:1, “b”:2, “c”:3} | {a=1, b=2, c=3}

and:

JSON object | term (colon)
--- | ---
{“a”:1, “b”:2, “c”:3} | {a:1, b:2, c:3}

By default, the curly-term representation and the dash pair representation are used. The `json/3` parametric object allows selecting the desired representation choices. For example:

    | ?- json(curly,dash,atom)::parse(atom('{"a":1, "b":2, "c":3}'), JSON).
    JSON = {a-1, b-2, c-3}
    yes

    | ?- json(list,equal,atom)::parse(atom('{"a":1, "b":2, "c":3}'), JSON).
    JSON = json([a=1, b=2, c=3])
    yes

    | ?- json(curly,colon,atom)::parse(atom('{"a":1, "b":2, "c":3}'), JSON).
    JSON = {a:1, b:2, c:3}
    yes

## Encoding

Encoding is accomplished using the `generate/2` predicate. For example:

    | ?- json::generate(codes(Encoding), [a,{b-c}]).
    Encoding = [91,34,97,34,44,123,34,98,34,58,34,99,34,125,93]
    yes

Alternatively:

    | ?- json::generate(chars(Encoding), [a,{b-c}]).
    Encoding = ['[','"',a,'"',',','{','"',b,'"',:,'"',c,'"','}',']']
    Yes

    | ?- json::generate(atom(Encoding), [a,{b-c}]).
    Encoding = '["a",{"b":"c"}]'
    Yes

Notice that `generate/2` takes, as second argument, a Prolog term that corresponds to the JSON Syntax in Prolog and produces the corresponding JSON output in the format specified in the first argument: (`codes(Variable)`, `stream(Stream)`, `file(File)`, `chars(Variable)` or `atom(Variable)`).

## Decoding

Decoding is accomplished using the `parse/2` predicate. For example, to decode a given json file:

    | ?- json::parse(file('simple/roundtrip_array_obj_array.json'), Term).
    Term = [{a-[b]}]
    yes

The `parse/2` predicate first argument must indicate the input source (`codes(Codes)`, `stream(Stream)`, `line(Stream)`, `file(Path)`, `chars(Chars)` or `atom(Atom)`) containing a JSON payload to be decoded into the Prolog term in the second argument.

## Known issues

Some tests, notably `parse_simple_valid_files` and `roundtrip_hexadecimals`, fail on backends such as ECLiPSe and GNU Prolog that don’t support Unicode.