Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool

Prolog JSON Parsing and Formatting Approaches

Handling data interchange formats like JSON is crucial for integrating Prolog applications with the broader developer ecosystem, including web services, APIs, and data processing pipelines. Fortunately, modern Prolog implementations provide robust libraries and predicates specifically designed for parsing JSON strings into Prolog terms and formatting Prolog terms into JSON strings.

This page explores the common approaches and provides examples using the widely supported syntax found in systems like SWI-Prolog, which offers a comprehensive JSON library.

The Basics: Representing JSON in Prolog

Before diving into parsing and formatting, it's essential to understand how JSON data structures are typically represented within Prolog's term structure:

  • JSON Object: Represented as a Prolog Dict (or "dictionary"). Keys are typically atoms or strings, and values can be any Prolog term representing a JSON value. Example: _{id:123, name:"Alice", active:true}
  • JSON Array: Represented as a standard Prolog List. Example: [1, "apple", false]
  • JSON String: Represented as a Prolog String (double quotes) or sometimes an Atom (single quotes), depending on the specific predicate and options used. Strings are generally preferred for direct JSON mapping. Example: "Hello, World!"
  • JSON Number: Represented as a Prolog Integer or Float. Example: 42, 3.14
  • JSON Boolean: Represented by the Prolog atoms true and false.
  • JSON Null: Represented by the Prolog atom null.

This mapping allows Prolog's powerful unification and pattern matching capabilities to be applied directly to parsed JSON data.

JSON Parsing: From String to Prolog Term

The primary goal of JSON parsing in Prolog is to take a JSON formatted string (or stream) and convert it into a corresponding Prolog term (usually a dict or a list). The most common predicate for this in SWI-Prolog is json_read/2 or json_read_dict/2.

Using json_read/2

json_read(Stream, Term) reads a JSON term from Stream and unifies it with Term. You typically wrap the string in a "string stream" for this.

Example 1: Parsing a JSON Object

?- json_read(string('{"name": "Bob", "age": 25}'), Term).
Term = _{name:"Bob", age:25}.

Example 2: Parsing a JSON Array

?- json_read(string('[10, "twenty", true, null]'), Term).
Term = [10, "twenty", true, null].

Example 3: Parsing Nested Structures

?- json_read(string('{"person": {"name": "Charlie", "cities": ["London", "Paris"]}, "active": false}'), Data).
Data = _{active:false, person:_{cities:["London", "Paris"], name:"Charlie"}}.

Using json_read_dict/2

This predicate is similar but specifically designed for parsing the top-level JSON object or array directly into a dict or list term. It's often more convenient for parsing JSON API responses which are typically either an object or an array at the root.

Example: Using json_read_dict/2

?- json_read_dict(string('{"status": "ok", "data": [1, 2, 3]}'), Dict).
Dict = _{data:[1, 2, 3], status:"ok"}.

?- json_read_dict(string('[{"id": 1}, {"id": 2}]'), List).
List = [_{id:1}, _{id:2}].

Accessing Parsed Data (using Dicts)

Once parsed into a Prolog dict, you can access elements using the Dict.Key syntax or unification.

Example: Accessing Dict Values

?- json_read(string('{"name": "David", "address": {"city": "Rome", "zip": "00100"}}'), Person).
Person = _{address:_{city:"Rome", zip:"00100"}, name:"David"}.

?- Person.name = Name.
Name = "David".

?- Person.address.city = City.
City = "Rome".

?- _{address:Address} = Person. % Using unification
Address = _{city:"Rome", zip:"00100"}.

Error Handling

If the input string is not valid JSON, the parsing predicates will typically throw an exception. You can use catch/3 to handle these errors gracefully.

JSON Formatting: From Prolog Term to String

Formatting (or serializing) JSON involves converting a Prolog term (that represents a JSON structure) back into a JSON formatted string. Predicates like json_write/2 and json_write_dict/2 are used for this purpose. They typically write to a stream, which can be an output stream like current_output or a "string stream" to capture the output into a Prolog string.

Using json_write/2 or json_write_dict/2

These predicates take a Prolog term and a stream and write the JSON representation to the stream. To get the JSON into a string variable, you use a "string stream" in write mode.

Example 1: Formatting a Prolog Dict

?- Term = _{product: "book", price: 19.95, inStock: true},
   open_string(StringStream, write, Stream),
   json_write(Stream, Term),
   close(Stream),
   string_codes(JsonString, StringStream).
Term = _{inStock:true, price:19.95, product:"book"},
StringStream = <stream>(0x...)-"",
Stream = <stream>(0x...),
JsonString = "{"product":"book","price":19.95,"inStock":true}".

Example 2: Formatting a Prolog List

?- Term = ["apple", 120, null, false],
   open_string(StringStream, write, Stream),
   json_write(Stream, Term),
   close(Stream),
   string_codes(JsonString, StringStream).
Term = ["apple", 120, null, false],
StringStream = <stream>(0x...)-"",
Stream = <stream>(0x...),
JsonString = "["apple",120,null,false]".

Example 3: Formatting Nested Structures

?- Data = _{config:_{timeout:5000, retries:3}, endpoints:["/users", "/products"]},
   open_string(StringStream, write, Stream),
   json_write(Stream, Data),
   close(Stream),
   string_codes(JsonString, StringStream).
Data = _{config:_{retries:3, timeout:5000}, endpoints:["/users", "/products"]},
StringStream = <stream>(0x...)-"",
Stream = <stream>(0x...),
JsonString = "{"config":{"timeout":5000,"retries":3},"endpoints":["/users","/products"]}".

Formatting Options (Pretty Printing, etc.)

Predicates like json_write/3 or json_write_dict/3 allow specifying options, such as indentation for "pretty printing" the output JSON string, making it more human-readable.

Example: Pretty Printing with json_write/3

?- Data = _{config:_{timeout:5000, retries:3}, endpoints:["/users", "/products"]},
   open_string(StringStream, write, Stream),
   json_write(Stream, Data, [json_option(indent(4))]), % Use 4 spaces for indent
   close(Stream),
   string_codes(JsonString, StringStream).
Data = _{config:_{retries:3, timeout:5000}, endpoints:["/users", "/products"]},
StringStream = <stream>(0x...)-"",
Stream = <stream>(0x...),
JsonString = "{\n    "config": {\n        "timeout": 5000,\n        "retries": 3\n    },\n    "endpoints": [\n        "/users",\n        "/products"\n    ]\n}". % Note: \n and indentation

When outputting to a console or file, the newlines and spaces would render the structured format.

Working with JSON Files

Reading from or writing to JSON files is straightforward using standard Prolog file handling predicates (open/3, close/1) in conjunction with the JSON library predicates.

Example: Reading from a File

% Assuming 'data.json' contains: {"id": 101, "status": "active"}
read_json_file(FilePath, Term) :-
    setup_call_cleanup(
        open(FilePath, read, Stream),
        json_read(Stream, Term),
        close(Stream)
    ).

% Example query:
% ?- read_json_file('data.json', Data).
% Data = _{id:101, status:"active"}.

Example: Writing to a File

% This will create 'output.json' with the formatted JSON
write_json_file(FilePath, Term) :-
    setup_call_cleanup(
        open(FilePath, write, Stream),
        json_write(Stream, Term, [json_option(indent(2))]), % Pretty print
        close(Stream)
    ).

% Example query:
% ?- Data = _{report:_{date:"2023-10-27", count:99}},
%    write_json_file('output.json', Data).

Conclusion

Prolog's built-in capabilities, especially the JSON libraries available in implementations like SWI-Prolog, make handling JSON data straightforward and efficient. By mapping JSON objects to Prolog dicts and JSON arrays to Prolog lists, developers can leverage Prolog's strengths in data representation, manipulation, and pattern matching directly on external data sources. Whether you need to parse data from a web API or format Prolog results for a web application, the predicates covered provide the necessary tools.

Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool