Skip to content
100% in your browser. Nothing you paste is uploaded — all processing runs locally. Read more →

JSON vs YAML vs JSONC vs NDJSON — when to pick which

On this page
  1. The contestants in one sentence each
  2. JSON — the boring default
  3. YAML — comments, anchors, and the Norway problem
  4. JSONC — JSON for humans
  5. NDJSON — JSON that streams
  6. Cheat sheet — pick by use case
  7. Two surprises worth knowing
  8. Further reading

Four formats, all called “structured text”, all overlapping in obvious ways. Pick one for the wrong reason and you’ll fight tooling forever. Pick the right one and the tooling is silent.

This is a working engineer’s decision guide. Not a comprehensive spec — the specs are linked at the bottom. Just: when to pick which, what breaks if you don’t, and the surprises in each.

The contestants in one sentence each

The surprises start when you assume one is the others.

JSON — the boring default

Use JSON when:

Don’t use JSON when:

The JSON spec is RFC 8259. It’s eight pages. Read it once.

A non-obvious part of “broad support”: field order is not preserved across most parsers. A JSON object { "a": 1, "b": 2 } may come back as { "b": 2, "a": 1 } after a round-trip. If order matters, encode it explicitly — as an array.

Try JSON formatting / minifying / validating at json.tooljo.com.

YAML — comments, anchors, and the Norway problem

Use YAML when:

Don’t use YAML when:

country: "NO"  # the string "NO"
country: NO    # the boolean false (in YAML 1.1)

YAML 1.2 narrowed this — only true, false, True, False are booleans — but most parsers still default to 1.1 mode. Always quote two-letter values that might be confused with booleans.

The biggest YAML footgun: indentation. Tabs are not allowed. Spaces must be consistent within a block. A misplaced two-space indent silently turns a sibling key into a child. Invest in an editor that highlights indentation, or use a linter.

JSONC — JSON for humans

Use JSONC when:

Don’t use JSONC for interchange. JSONC is a strict superset of JSON, but most JSON parsers reject the comments and trailing commas. The file extension .jsonc signals to tooling that the file may contain those features; .json does not. Mismatch the two and CI will fail at the first comment.

The two extensions that make JSONC valid:

That’s it. No multi-line strings, no anchors, no references.

For tooling support: VS Code, Microsoft’s jsonc-parser library, and Deno all parse JSONC out of the box. Most server-side languages need a JSONC-aware library (in Python, json5 covers more than JSONC but works). Strip-then-parse is also reasonable for one-off scripts:

// strip block + line comments and trailing commas, then JSON.parse
function loadJsonc(text) {
  return JSON.parse(
    text
      .replace(/\/\*[\s\S]*?\*\//g, "")
      .replace(/\/\/.*$/gm, "")
      .replace(/,(\s*[\]}])/g, "$1"),
  );
}

(Don’t ship that in production — it breaks if a // appears inside a string. Use a real JSONC parser. But for ops scripts, it’s enough.)

NDJSON — JSON that streams

Use NDJSON when:

The format is exactly what it sounds like: one valid JSON value per line, separated by \n. No surrounding array, no commas between records.

{"ts":1717000000,"level":"info","msg":"started"}
{"ts":1717000001,"level":"warn","msg":"retry","attempt":2}
{"ts":1717000002,"level":"error","msg":"failed","err":"timeout"}

This is the format Kubernetes uses for kubectl logs, what Loki ingests, what most log shippers expect. It composes beautifully with Unix tools: grep, jq, awk all work line-by-line.

The trap with NDJSON: don’t pretty-print individual records. The “one value per line” contract is broken if a single value spans multiple lines. Use a serializer that always emits compact JSON.

There’s a related-but-different format called JSON Lines (.jsonl). It’s the same thing as NDJSON for practical purposes — both are line-delimited JSON. Pick one extension and be consistent.

Cheat sheet — pick by use case

Use casePick
HTTP API request/response bodyJSON
CI/CD pipeline configYAML
App config edited by humansJSONC
Application logs / streaming eventsNDJSON
Kubernetes / Helm / Docker ComposeYAML
Browser localStorage payloadsJSON
package.json, tsconfig.jsonJSONC
Big dataset / analytics exportNDJSON
Inter-service RPC payloadJSON
GitHub Actions workflowYAML

Two surprises worth knowing

Indentation in YAML can change semantics. This works:

deploy:
  steps:
    - name: build
    - name: test

This silently breaks:

deploy:
  steps:
   - name: build
   - name: test

The second has 3-space indents. Some parsers accept it; some don’t. CI failures from “your YAML is valid on my machine” usually trace back to this. Use 2- or 4-space indents, never odd numbers.

JSON’s null and JavaScript’s undefined are not the same. JSON has no undefined. JSON.stringify({ a: undefined }) produces "{}", not '{"a":null}'. If you need explicit nulls — say, to clear a database field — write the null yourself.

Further reading

For interactive JSON conversion (formatter, minifier, JSON ↔ YAML), see json.tooljo.com.