JSON Toolkit

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.

Beginner~10 min readPublished 2026-10-30Updated 2026-10-30
Editors, command line, and automation toolchains cover

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:

  1. json() provides syntax highlighting and bracket matching — do not write your own;
  2. jsonParseLinter() produces line/column-level error markers; pair it with lintGutter() to show icons next to the line numbers;
  3. The updateListener must be debounced; calling JSON.parse on a hundred thousand lines on every keystroke will visibly lag;
  4. Large documents need optimizations beyond EditorView.lineWrapping to 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

  1. Use jq -e as an assertion: the exit code is 1 when the expression result is false or null, so it can go straight into scripts as a check;
  2. jq 'paths' lists all paths, giving you a quick overview of an unfamiliar document's structure;
  3. jq '.. | objects | keys' finds every key name that has ever appeared, which is handy for hunting down inconsistent spelling;
  4. Format before comparing in the editor: an unformatted diff is pure noise; a structural comparison such as /diff is more accurate.

6. Checklist

  • [ ] Editor error markers are precise to line and column, and parsing is debounced;
  • [ ] Team scripts pass arguments with jq --arg instead 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.json or .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.