Read and write JSON¶
The core reads and writes only YAML. JSON and JSON Lines come from a
sibling module, config-json, so a consumer
who needs JSON takes it and one who does not pays nothing for it.
import (
"gitlab.com/phpboyscout/go/config"
configjson "gitlab.com/phpboyscout/go/config-json"
)
store, err := config.NewStore(ctx,
config.WithFiles(fsys, "/etc/app.yaml"), // YAML, the default
config.WithBackend(configjson.New(fsys, "/etc/app.json")), // outranks it
config.WithBackend(configjson.NewLines(fsys, "/var/lib/app/state.jsonl")),
)
A JSON layer takes part in precedence, per-key merge, provenance and write routing exactly as a
YAML file does. New reads a single JSON document; NewLines reads JSON Lines — one object per
line.
Reading¶
Reading decodes with the standard library, so it adds no dependency at all. A JSON document's
top level must be an object: an array or a scalar cannot carry configuration keys, and is
refused at load with config.ErrBackendUnsafe naming the file rather than failing obscurely
later.
Writing preserves the file¶
The reason this is not just json.Marshal is the reason the whole toolkit exists.
Re-serialising a decoded document alphabetises the keys and discards the formatting — the
merged-view write structure-preserving writes exist to
avoid. So a write edits the document in place:
One value changed. An edit to a key that already exists leaves every other byte — key order,
indentation, the untouched host, the trailing newline — exactly as it was.
Adding a new key is the one case that touches formatting: it is inserted and the document re-indented to its own detected style (two-space, four-space, tabs), because the alternative is a key jammed in without matching its neighbours. A single-line document stays single-line.
JSON has no comments to lose, so that is the whole of the fidelity contract: key order always survives; indentation is preserved on an edit and matched on an insertion.
JSON Lines is several layers¶
One line is one document, the same model a multi-document YAML file uses: the line index is the
document index, provenance distinguishes them (state.jsonl#1), and a later line outranks an
earlier one for the same key. An edit targets a specific line and leaves the others
byte-identical.
What it costs¶
Reading is standard-library only. Writing adds tidwall/sjson (edit in place) and
tidwall/pretty (re-indent an inserted key) — four small packages, asserted by an allowlist
test in the module so an unforeseen transitive addition fails its build. No filesystem library:
you supply the config.FS.
Related¶
- Support a new file format — write an adapter for a format not yet
covered;
config-jsonis the worked example - What survives a write — the preservation contract JSON is held to