Editors, Command Line, and Automation Toolchains
Bottom line: Editors solve "see it clearly, edit it correctly", while the command line solves "batch, repeatable". Each side has a few tools that stay useful for a decade.

Bottom line: pick the editor by scenario — CodeMirror 6 for utility sites and mobile, Monaco only when you need the full IDE experience; leave batch work to jq plus three CI checks for syntax, structure, and style, and always pass arguments with jq --arg.
The era of checking JSON by hand should be over: anything a machine can repeat, do not leave to human eyes.
1. Editors in the browser
1.1 Minimal CodeMirror 6 integration
import { EditorView, basicSetup } from 'codemirror';
import { json, jsonParseLinter } from '@codemirror/lang-json';
import { linter, lintGutter } from '@codemirror/lint';
const view = new EditorView({
parent: document.getElementById('editor')!,
doc: '{"hello": "json"}',
extensions: [
basicSetup,
json(),
linter(jsonParseLinter()),
lintGutter(),
EditorView.updateListener.of((update) => {
if (update.docChanged) {
// Debounce before doing heavy work; do not format on every keystroke
scheduleParse(update.state.doc.toString());
}
}),
],
});
Four points to note:
json()provides syntax highlighting and bracket matching — do not write your own;jsonParseLinter()produces line/column-level error markers; pair it withlintGutter()to show icons next to the line numbers;- The
updateListenermust be debounced; callingJSON.parseon a hundred thousand lines on every keystroke will visibly lag; - Large documents need optimizations beyond
EditorView.lineWrappingto avoid rendering the whole thing at once (CodeMirror 6 renders by viewport by default, which is an improvement over older versions).
1.2 Folding and outlines
CodeMirror 6 has built-in folding; in JSON scenarios you usually fold by "object/array":
import { foldGutter, foldKeymap } from '@codemirror/language';
import { keymap } from '@codemirror/view';
With foldGutter() and keymap.of(foldKeymap), users can collapse long arrays with a keyboard shortcut.
1.3 Choosing between CodeMirror and Monaco
| Dimension | CodeMirror 6 | Monaco |
|---|---|---|
| Bundle size | Small, modular | Large (several MB) |
| Mobile | Well supported | Mediocre experience |
| Language services | Must build your own | Rich, built in |
| Customization cost | Low | Medium |
Utility sites and mobile-first → CodeMirror; full IDE experience (multiple files, go-to-definition, completion) → Monaco.
2. Command line: six high-frequency jq recipes
jq is to JSON what sed + awk are to text; learning just a small part of its syntax covers eighty percent of everyday needs.
# 1. Pick fields (the most common)
jq '.data.items[].name' response.json
# 2. Filter + reshape
jq '.data.items[] | select(.price < 10) | {id, price}' response.json
# 3. Aggregates (length, sum, max)
jq '[.items[] | .price] | {count: length, total: add, max: max}' response.json
# 4. Group and aggregate
jq '[.items[]] | group_by(.category) | map({category: .[0].category, count: length})' response.json
# 5. Merge multiple files into an array
jq -s '.' part-*.json > merged.json
# 6. Process JSON Lines line by line
jq -c 'select(.level == "error")' app.jsonl
# Common output switches
jq -r '.data.name' # Strip the quotes so it pipes cleanly into the shell
jq -c '.' # Compact output, one line
jq --arg id "$ID" '.id == $id' # Pass arguments safely, avoiding injection through concatenation
--arg deserves a special mention: concatenating a variable directly into an expression breaks as soon as the value contains quotes, whereas --arg lets jq handle the escaping.
2.1 Other tools at a glance
| Tool | Purpose | In one sentence |
|---|---|---|
yq |
YAML/JSON/XML conversion | Syntax similar to jq, the workhorse for configuration files |
dasel |
Unified querying across formats | Supports JSON/YAML/TOML/XML/CSV |
fx |
Interactive JSON browsing | Click around in the terminal, good for exploring |
jless |
Paged browsing in the terminal | Like less, with folding and search |
gron |
Flatten JSON into greppable lines | A great tool for figuring out "which level is this field on" |
ajv-cli |
Schema validation | Validate JSON files in CI |
The idea behind gron is especially worth knowing:
$ echo '{"a":{"b":[1,2]}}' | gron
json = {};
json.a = {};
json.a.b = [];
json.a.b[0] = 1;
json.a.b[1] = 2;
Once flattened, grep can locate the path, which is much faster than expanding level by level in an editor.
3. Three layers of validation in CI
| Layer | Tool | What a failure means |
|---|---|---|
| Syntax | JSON.parse / jq empty |
The file itself is malformed |
| Structure | ajv-cli + schema |
Missing fields, wrong types |
| Style | Prettier | Inconsistent formatting, noisy diffs |
# Syntax: batch-validate every json file in the repository
find . -name '*.json' -not -path './node_modules/*' -print0 \
| xargs -0 -n1 jq empty
# Structure: validate against a schema
npx ajv-cli validate -s schemas/order.json -d 'fixtures/orders/*.json' --strict=false
# Style: check only, do not modify
npx prettier --check "**/*.json"
You need all three layers, because their error messages are completely different:
jq empty only says "which line is broken", ajv says "which field violates which keyword", and Prettier only says "the formatting is inconsistent".
4. pre-commit hooks
# .husky/pre-commit example (excerpt)
npx lint-staged
{
"lint-staged": {
"*.json": ["prettier --write", "jq empty"]
}
}
Be careful with tool configuration files such as package.json and tsconfig.json; do not let Prettier re-indent them casually —
some tools are sensitive to formatting (valid JSON should not be, but in practice this has caused problems). You can exclude them individually.
5. Debugging tips
- Use
jq -eas an assertion: the exit code is 1 when the expression result isfalseornull, so it can go straight into scripts as a check; jq 'paths'lists all paths, giving you a quick overview of an unfamiliar document's structure;jq '.. | objects | keys'finds every key name that has ever appeared, which is handy for hunting down inconsistent spelling;- Format before comparing in the editor: an unformatted diff is pure noise; a structural comparison such as
/diffis more accurate.
6. Checklist
- [ ] Editor error markers are precise to line and column, and parsing is debounced;
- [ ] Team scripts pass arguments with
jq --arginstead of concatenating strings; - [ ] CI has all three validation layers (syntax/structure/style);
- [ ] pre-commit only processes staged files, so a full scan does not slow down commits;
- [ ] Tool versions are pinned in
package.jsonor.tool-versions.
FAQ
How do I choose between CodeMirror 6 and Monaco?
For utility sites and mobile, prefer CodeMirror 6: it is small, modular, and well supported on mobile, and syntax highlighting, bracket matching, and folding can be composed as needed. Reach for Monaco only when you need the full IDE experience with multiple files, go-to-definition, and completion — at the cost of a multi-megabyte bundle and higher customization effort.
Why does CI need syntax, structure, and style checks together?
The three layers report completely different errors: jq empty says which line is broken, ajv says which field violates which keyword, and Prettier only says the formatting is inconsistent. Dropping one layer makes the others' errors easy to misread, so keep all three and stop as soon as a layer fails.
Should I rely on pre-commit instead of validating in CI?
No. Hooks process staged files only, which gives feedback within a minute but cannot cover the full repository or changes that bypass the commit. CI is responsible for completeness: keep pre-commit on staged files and leave full scans to CI.