jq++: JSON with inheritance and templating

jq++ is a tool that extends your JSON with file- and node-level inheritance, templating, and reference evaluation. It productizes the ideas of jq-front, which was named after Cfront[3]—the classic C++-to-C translator—and gives JSON a similar “front-end” power.

Despite criticism of JSON[6] as a format for configuration[4], JSON has clear strengths: broad tool support, a good balance between machine and human readability, and simple, unambiguous syntax.

Where JSON falls short is in structure. When you need many similar but slightly different configurations, redundancy is hard to remove because JSON does not define relationships between files or between nodes inside a file.[1]

What is it good for?

Have you ever had to maintain configuration files that are almost the same, but not quite?

config for foo config for bar
{
  "foo_database": {
    "server": {
      "ip": "192.168.1.5",
      "port": 2001
    },
    "db_name": "foo",
    "user": {
      "name": "root",
      "password": "foo_root"
    }
  }
}
{
  "bar_database": {
    "server": {
      "ip": "192.168.1.5",
      "port": 2001
    },
    "db_name": "bar",
    "user": {
      "name": "root",
      "password": "bar_root"
    }
  }
}

With jq++, you can define a single base JSON for the common parts:

database_default
{
    "server": {
      "ip": "192.168.1.5",
      "port": 2001
    },
    "user": {
      "name": "root"
    }
}

Then reuse it from other files via $extends:

foo.json bar.json
{
  "foo_database": {
    "$extends": [ "database_default.json" ],
    "db_name": "foo",
    "user": {
      "password": "foo_root"
    }
  }
}
{
  "bar_database": {
    "$extends": [ "database_default.json" ],
    "db_name": "bar",
    "user": {
      "password": "bar_root"
    }
  }
}

Run jq foo.json` and you get the expanded “config for foo” from the first table; `jq bar.json gives you the “config for bar”. You keep one source of truth and generate each variant by extending it.

Templating and references

jq++ also supports templating and references so you can embed values and run shell commands in string fields.

Example: extend two files and build a greeting string from their fields and the current date:

sayHello.json
{
  "$extends": ["greeting.json", "name.json"],
  "sayHello": "eval:refexpr(\".greeting\") + \", \" + refexpr(\".yourname\") + \". Today is \" + (now|todate)"
}

With greeting.json as {"greeting":"Hello"} and name.json as {"yourname":"Mark"}, running jq++ sayHello.json produces:

{
  "greeting": "Hello",
  "sayHello": "Hello, Mark. Today is 2026-02-14T13:07:57Z",
  "yourname": "Mark"
}

YAML?

JSON is often criticized as a configuration language[4]. Whether or not you agree, jq can still improve a YAML-based workflow: you keep YAML for editing, then convert to JSON, run jq for inheritance and templating, and optionally convert back to YAML.

INPUT OUTPUT
$local:
  database_default:
    server:
      ip: 192.168.1.5
      port: 2000
    db_name: test
    user:
      name: root
      password: root

foo_database:
  $extends: [ database_default ]
  server:
    port: 2001
  db_name: foo
  user:
    password: foo_root
foo_database:
  server:
    ip: 192.168.1.5
    port: 2001
  db_name: foo
  user:
    name: root
    password: foo_root

Example pipeline (using yq for YAML↔JSON):[8]

$ yq . -j in.yaml | jq++ | yq . -y

HOCON?

HOCON[hocon] is another human-friendly format with inheritance and references. You might still prefer jq++:

  • Standard JSON in and out. jq++ reads and writes JSON, which fits into almost any toolchain.

  • Custom behavior via templating. You can define how values are computed (references, eval:, etc.) without leaving JSON.

  • Separation of concerns. Data structure (inheritance, references) and human-friendly syntax are different problems; jq++ focuses on structure so you can choose the syntax (JSON, YAML, etc.) separately.

That separation matters in automation and CI/CD. Configs often need to be generated by programs; JSON is easy to generate and parse. HOCON, as a superset of JSON, is less straightforward to emit: a program might produce valid HOCON that looks different from hand-written style after a round trip. If you use jq++ for the “structure and value resolution” step and keep JSON (or YAML) as the interchange format, you avoid mixing style and semantics in one layer.

You can combine jq++ with any JSON-oriented tools—including jq[7] for querying and transformation—and still improve readability by editing in YAML or another format upstream.

References

top


1. This limitation is not unique to JSON; YAML, for example, can reference object nodes but cannot embed one text value inside another by itself.