JSON Module¶
The py.json standard library module provides relational predicates for parsing, generating, and querying JSON data. JSON objects map to DictTerm for unification-aware access.
The implementation lives in clausal/modules/py/json.py.
Import¶
Or via module import:
Type Mapping¶
| JSON | Clausal Prolog |
|---|---|
{} object |
DictTerm with atom keys |
[] array |
Python list |
"string" (a value) |
a string (in Python, the ('$chars', text) carrier) |
123 / 1.5 |
Python int / float |
true/false |
Python True/False |
null |
Python None |
An object key is a name, so it comes back as an atom: D.name and
get(D, name, V) read a parsed object (declare name, or write 'name').
A string key get(D, "name", V) does not match the atom key, and the goal
fails. A string value is text, so it
comes back as a string. parse/3's atoms(...) option (below) is how you
promote chosen values to atoms as well.
Generating is the mirror: an atom serialises as the JSON string of its
spelling, a string as itself, and a compound cell — which has no JSON
counterpart — raises type_error(json_term, Cell).
Conversion is recursive: nested JSON objects produce nested DictTerms, and
the atoms vocabulary reaches every nested string value.
Predicates¶
parse/2¶
parse(String, Term) — parse a JSON string into Clausal Prolog terms. Text that is not JSON raises syntax_error(invalid_json) (ruled 2026-10-02; it was domain_error(json_text, S)); an unbound String raises instantiation_error.
parse/3¶
parse(String, Term, Options) — parse/2 plus a vocabulary of string values
that should come back as atoms. The only option is atoms(Spellings),
where Spellings is a list of the texts to mint; anything else raises
domain_error(json_option, Opt).
# Keys are atoms; string values are strings.
test("keys are atoms, values are strings") <- (
parse("{\"colour\": \"red\", \"note\": \"hi\"}", D),
V is D.colour,
string(V),
V == "red"
) # nv
# 'atoms'(["red"]) promotes just that value to the atom red. The option
# term is data, so the module carries -implicit_functors (or declares
# atoms/1 in -private).
test("the atoms option mints a vocabulary") <- (
parse("{\"colour\": \"red\", \"note\": \"red flag\"}", D2, ['atoms'(["red"])]),
C is D2.colour,
atom(C),
C is red,
N is D2.note,
string(N) # not in the vocabulary -- still text
) # nv
Use it when a JSON document carries a closed vocabulary — a status, a colour, an enum — that the program wants to reason about as symbols rather than as text. Everything not listed stays a string.
generate/2¶
generate(Term, String) — serialize a Clausal Prolog term to a compact JSON string. Fails if the term contains unbound variables. An atom serialises as the JSON string of its spelling; a compound cell raises type_error(json_term, Cell).
# Generating is the mirror: an atom becomes the JSON string of its spelling.
test("an atom generates as a JSON string") <- (
generate({colour: red}, S),
S == "{\"colour\": \"red\"}"
) # nv
pretty_generate/2¶
pretty_generate(Term, String) — like generate but with 2-space indentation.
get/3¶
get(Term, Key, Value) — extract a value from a DictTerm by key.
- Key bound: direct lookup, unify Value. Fails if key not found. Keys of a parsed object are atoms.
- Key unbound: enumerate all key-value pairs via backtracking.
-import_from(py.json, [parse, get])
get_name(JSON_STRING, NAME) <- (
parse(JSON_STRING, DATA),
get(DATA, 'name', NAME) # the atom key; "name" would fail
)
Given the text {"name": "Ann"}, NAME is the string "Ann"; with KEY
unbound, get(DATA, KEY, V) enumerates KEY = name, V = "Ann".
read_file/2¶
read_file(Path, Term) — read and parse a JSON file. A file that is not JSON raises syntax_error(invalid_json), one that is not UTF-8 syntax_error(invalid_data) (both were domain_error(json_file, Path)); a file-system failure raises the ISO error (py.files: existence_error(source_sink, Path), ...).
write_file/2¶
write_file(Path, Term) — serialize a term and write to a JSON file (2-space indented). Fails if term contains unbound variables.