Design Principles

JSON++ was designed around four principles.

Principle 1 — Completeness and Compatibility

Any JSON can be represented as JSON++: An important property of JSON++ is that it does not restrict the space of configurations that can be produced. The jq++ evaluation function maps JSON++ documents to ordinary JSON documents. For any valid JSON document J, there exists a JSON++ document X such that jq++(X) = J. In other words, the evaluation function is surjective onto JSON. This property ensures that JSON++ can generate any configuration expected by downstream systems, allowing jq++ to be introduced as a pre-processing step without reducing expressiveness.

In practice, this means that adopting JSON++ never forces downstream tools to change their expectations about the resulting configuration structure.[1]

All JSON++ documents are valid JSON: Moreover, JSON++ documents remain valid JSON, which allows existing parsers and tooling to process them without modification.

Principle 2 — JSON Data-Model Compatibility

JSON++ operates at the level of the JSON data model rather than a specific surface syntax. Because many configuration languages such as YAML, HCL, and TOML are routinely translated into JSON during processing, the same reuse mechanisms can be applied to configurations written in those formats. In practice this means that JSON++ concepts naturally extend to JSON-compatible representations such as YAML without requiring changes to the authoring language.

This also implies that the same extension concept naturally applies to other JSON-compatible formats such as YAML.

Principle 3 — Declarative Structural Reuse

JSON++ introduces explicit constructs for expressing structural reuse.

  • Structural composition constructs ($extends, $includes) let configurations combine documents directly, describing relationships in the data structure itself rather than through external tooling.

  • Expression evaluation (eval:) lets configurations compute values dynamically from the composed result.

Principle 4 — Same-Kind Merge

A merge is defined only between two values of the same kind: When jq++ combines two values by merging, both values must be of the same kind: object with object, array with array, or atom with atom. Strings, numbers, booleans, and null are all atoms. jq++ never merges values across kinds. Where a construct requests a merge and the operand kinds differ, jq++ reports a configuration error rather than guessing an outcome.

This principle rests on the distinction between overriding and merging:

  • An override replaces the inherited value entirely. Writing a key in a child object means "override"; no kind constraint applies, and the child value wins regardless of its type.

  • A merge combines the inherited value with the child value. Merging is only meaningful within a kind, so requesting it across kinds is a contradiction to be reported, not a replacement to be performed silently.

Today the only merge the engine performs is between two objects; every other combination during inheritance is an override. Future constructs that extend merging to arrays and atoms are bound by this principle: they merge within a kind and report an error across kinds.


1. raw: is the guaranteed escape mechanism for values or keys that would otherwise be treated as JSON++ control syntax. jq++ also keeps inheritance-time keywords in the $…​ namespace and treats new keyword families as a major-version compatibility change.