Writing Criteria
Every guide in this repo is written to the same standard. If you're adding or editing a guide — human or AI — hold it to these.
- Plain words, real ideas. Simple language, never dumbed-down concepts. Explain jargon on first use; collect terms in the glossary.
- No hype. No marketing adjectives, no "revolutionary," no promises about what something will do for the reader.
- Honest about limits. Say what the thing can't do. State uncertainty instead of smoothing it over. Validation notes include what remains unverified.
- No invented numbers. Benchmarks, defaults, and specs are either measured or qualified with "depends on your setup." If it wasn't tested, say so.
- Checkable. Commands are meant to be run, with "Check:" blocks showing what success looks like. Sources are linked, not asserted.
- Respect the reader's setup. Call out Windows/Mac/Linux differences. Don't assume a GPU, a budget, or prior experience.
- Safety isn't skipped. Real risks get stated plainly, not buried.
- Scoped. Each guide says what it covers and points elsewhere for the rest. No guide pretends to be the whole subject.
- Structure over prose. Tables, before/after examples, symptom → fix. Scannable beats essay.
Verification notes for this guide
Standard 3 and 4 applied to this guide, specifically. This is the rare guide with nothing in the "documented-only" column: jq's entire everyday surface is one binary in a terminal, so all of it ran.
The tool: jq-1.7, already installed on the
build machine at /usr/bin/jq (the distro
package; nothing was downloaded). Every command
in every chapter was captured from that binary.
The data: all sample files in examples/ are
invented for this guide — five fictional models,
eight fictional machines, three orders, eight log
events, one service config. models-min.json was
produced from models.json by jq -c '.', so the
two are the same JSON by construction. Because
the data is fictional, no capture needed
sanitising; commands were run from inside
examples/ so file names appear exactly as a
reader would type them.
Verified by running (every quoted output is a capture, transcribed into the chapter):
- Pretty-printing a minified file with
., compacting with-c, key order preserved on output, and-Ssorting keys. - Validation:
broken.json(a missing comma) produced jq's real parse error — "Expected separator between values at line 4, column 37" — and exit code 5. - Paths: nested keys, negative indexes, slices,
.[]iteration,.[]?,keys,lengthon arrays/objects/strings,type,to_entries, andnullfor a missing key, a missing top-level key, and indexing intonull. selectwith>=,==,and/or, and astartswithtest;//replacing an absent key in a stream and as// []inside a reshaping;hascounting 4 of 5;inon a literal object;type == "array"excluding the one machine whose key is absent.- Reshaping: constructed objects with computed
fields (context length ÷ 1024 → 32/32/8/4/16),
order totals from line items (50, 14.5, 40 —
chosen to be exact in decimal), the
{id}shorthand, array construction, interpolation with and without-r,@csvand@tsvrows, and@base64of a model name. - Aggregation over the inventory:
map(.ram_gb)totalling 360 and averaging 45,sort_by,uniqueon roles and OSes,unique_by(.os),reverse,max,min_bynaming garnet-07, andgroup_by(.location)counting 2/4/2. flatten: families levelled and deduplicated, and the all-the-way-down behaviour on[[1, [2, 3]], 4]. Also verified:group_byin 1.7 sorts by its key itself (shuffled input still grouped whole), despite the traditional idiom sorting first — the chapter says so.- The worked example end to end, step by step: free-percent roundings (21/10/73/9) hand-checked against the inventory (412/2000 = 20.6%), the sorted table, the under-15% watchlist (the same two names), and the summary object (8, 4, 360).
- Streams: per-value filtering of
events.json,lengthanswering 4 eight times (four keys per event),-sanswering 8, the error count of 2, slurping across two files (8 + 1 = 9), andinput/[inputs]under-n. -eexit statuses, shown with$?:true→ 0,false→ 1, no output → 4.- Feeding data:
--arg(count 4),--argjson(five machines ≥ 32 GB), both together (three servers), the--arg-is-a-string /--argjson-is-a-numbertypepair,$ENVwith a variable set for the command,--slurpfilecounting 8 events,--args/$ARGS, andjq -f summary.jqproducing the shipped summary — which cross-checks against chapters 5 and 6 (360 GB, roles 2/4/2, same low-disk pair). - Changing JSON, on
settings.json:=assignment (literal and document-referenced), the file provably unchanged afterwards (retriesstill 3),|=incrementing to 4,|=with a document reference erroring for real ("Cannot index number with string") — which is the chapter's=/|=contrast, and a better one than a silent difference would have been.delon a key and on an array element, andpathslisting all ten paths. - The destructive redirect, run on a throwaway
copy:
jq … scratch.json > scratch.jsonleft a 0-byte file, exit code 0, no message. The safe ritual (> settings.new.json && mv) was then run for real and read back (retries→ 5); the shippedsettings.jsonwas restored to its original bytes afterwards, so readers start from 3. - Errors, all captured with exit codes: the
compile error for an unfinished filter (exit
3, with jq's filter echo), "Cannot iterate over
null" ending a run after five real output lines
(exit 5), the three fixes (
[]?,// [],try … catch) each producing the full seven lines, "number … and string … cannot be added", "Cannot index array with string", and object + array addition refused. Also verified: mixed arrays sort without complaint ([3, "ten", 1]→[1, 3, "ten"]) under jq's total order — the expected "cannot be sorted" error does not exist, and the chapter says so rather than inventing one. - Output options:
--tab(the capture contains real tab characters), and number handling:9007199254740993printed as written, and9007199254740992after+ 0— the literal is preserved until arithmetic converts it to a double. - Scripts: a
-rstream feeding awhile readloop (five lines), anif jq -e …branch taken, a value captured into a shell variable, and thepipefailpair — the same failing filter in a pipeline exits 0 withoutpipefailand 5 with it, withwc -lcounting the same five partial lines both times.
Documented-only (not run), and why:
- The
curl | jqpipeline in chapter 11 is shown as a pattern, not a capture: no external API was called. The chapter says so in place, and uses the shippedmodels.json— the same shape of response — for every actual output. - Colour output (
-C/-M):-Mwas run and matches plain output; colour itself can't be shown in print, and the chapter says to try it on the reader's own terminal rather than describing shades. - Installation on the reader's machine
(
apt/brew/release binary) is described, not performed — the build machine already had jq. --seqframing,--stream, recursive definitions, modules, and the date builtins are named and scoped out in the text, not taught.
A note on versions: exit code 5 for parse errors is jq 1.7's behaviour as captured; older 1.6 installs differ in a few message wordings and in exit-code details. Where a claim in the chapters depends on the version, the text says 1.7.