{ }jsonkitOpen the tool

JSON5, JSONC, and NDJSON Explained

8 min read · updated 2026-07-12

Plain JSON is deliberately minimal. The RFC 8259 grammar has no comments, no trailing commas, and strict quoting rules — great for machines exchanging data, less pleasant for humans editing config files or systems streaming millions of records. That gap is why a family of JSON variants exists.

This guide covers the three you will actually meet in the wild: JSON5, JSONC, and NDJSON. Each solves a different problem. JSON5 makes JSON comfortable to write by hand. JSONC adds just comments for tooling config. NDJSON reshapes JSON for streaming and logs. We will look at concrete syntax, when to reach for each, and the one thing they all share — none of them are valid standard JSON, so a normal parser will reject them.

Why JSON Has Spawned So Many Variants

JSON became the default data format precisely because it is small and rigid. The specification fits on a few pages, every conforming parser agrees on what is valid, and there are no dialects to negotiate. That rigidity is a feature for machine-to-machine exchange, but it creates friction the moment a human has to read or write JSON directly.

Three pain points come up again and again: you cannot leave a comment explaining why a value is set the way it is; a trailing comma after the last item is a syntax error that complicates diffs and reordering; and streaming a large array forces a parser to hold the whole thing in memory. The variants below each attack one of these problems while staying close enough to JSON that existing tooling mostly still works.

VariantAddsPrimary use
JSON5Comments, trailing commas, unquoted keys, single quotes, moreHand-edited config
JSONCComments (and trailing commas in practice)Editor/tooling config (tsconfig, VS Code)
NDJSONOne JSON value per lineLogs, streaming, append-only data

JSON5: A Human-Friendly Superset

JSON5 is the most permissive of the three. It is a strict superset of JSON — every valid JSON document is also valid JSON5 — that borrows syntax from ECMAScript 5 to make configuration comfortable to write. The headline additions are comments, trailing commas, unquoted object keys, and single-quoted strings, but there is more: hexadecimal numbers, leading and trailing decimal points, explicit plus signs, Infinity and NaN, and multi-line strings via backslash line continuation.

config.json5 — every commented line here is invalid in plain JSONjson5
{
  // Line comments and /* block comments */ are allowed
  name: 'JsonKit',        // unquoted key, single-quoted string
  version: 2,
  ratio: .5,              // leading decimal point
  flags: 0xFF,            // hexadecimal number
  offset: +1,             // explicit plus sign
  limit: Infinity,        // not a valid JSON value
  tags: [
    'validate',
    'format',             // trailing comma is fine
  ],
}

Because it is a superset, JSON5 needs its own parser. In JavaScript the reference implementation is the json5 npm package, which mirrors the built-in JSON API:

Parsing and serializing JSON5 in Node.jsjs
import JSON5 from "json5";

const config = JSON5.parse(`{
  // trailing commas and comments survive the parse
  host: 'localhost',
  port: 8080,
}`);

console.log(config.port); // 8080

// JSON.stringify always emits strict, universally readable JSON
console.log(JSON.stringify(config)); // {"host":"localhost","port":8080}

JSONC: JSON With Comments

JSONC is a narrower idea: JSON plus comments, nothing more ambitious. The name comes from Microsoft, and it is the format behind some of the most-edited config files in the ecosystem — tsconfig.json, VS Code's settings.json, and .vscode/launch.json. In practice most JSONC parsers also tolerate trailing commas, but the defining feature is the ability to annotate config with // line and /* block */ comments.

tsconfig.json — valid JSONC, rejected by a strict JSON parserjsonc
{
  "compilerOptions": {
    // Match the runtime your code ships to
    "target": "ES2022",
    "module": "ESNext",
    "strict": true, /* enables every strict-mode check at once */
    "skipLibCheck": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

JSONC vs JSON5

The two overlap on comments and trailing commas, so they are easy to conflate. The distinction: JSONC stops there, keeping keys quoted and strings double-quoted, so it looks like ordinary JSON with notes. JSON5 goes further, allowing unquoted keys, single quotes, hex numbers, and Infinity. If you only need comments in a tooling config, JSONC keeps the file visually identical to JSON; JSON5 is for when you want the writing experience to feel like an ES object literal.

Editors handle JSONC by treating it as a distinct language mode. To feed a JSONC file to a tool that expects strict JSON, strip the comments first — a formatter can normalize it for you.

{ }JSON FormatterOpen the tool

NDJSON and JSON Lines: One Value Per Line

NDJSON (Newline-Delimited JSON), also known as JSON Lines or JSONL, solves an entirely different problem than the previous two. Instead of relaxing syntax, it changes structure: each line is a complete, independent JSON value, separated by a newline (\n). There is no enclosing array and no commas between records.

events.ndjson — three independent records, one per linetext
{"id":1,"event":"login","ts":"2026-07-12T10:00:00Z"}
{"id":2,"event":"click","ts":"2026-07-12T10:00:03Z"}
{"id":3,"event":"logout","ts":"2026-07-12T10:01:20Z"}

This shape has real advantages for logs and data pipelines. You append a new record by writing one more line — no need to rewrite a closing bracket. A consumer can process the stream line by line without loading the whole file into memory, which matters when the file is gigabytes of log data. And a single corrupt line does not invalidate the entire document; you can skip it and keep going. It is the native format for tools like BigQuery imports, many logging systems, and streaming APIs.

Streaming an NDJSON file line by line in Node.jsjs
import fs from "node:fs";
import readline from "node:readline";

const rl = readline.createInterface({
  input: fs.createReadStream("events.ndjson"),
  crlfDelay: Infinity,
});

for await (const line of rl) {
  if (!line.trim()) continue; // ignore blank lines
  const record = JSON.parse(line); // each line IS valid JSON
  console.log(record.event);
}

Why Standard Parsers Reject All of These

The most important thing to internalize: JSON5, JSONC, and NDJSON are not JSON. The RFC 8259 grammar has no production for comments, forbids trailing commas, requires double-quoted keys and strings, and treats a document as a single value. Feed any of these variants to a compliant parser and it will throw.

What JSON.parse does with each variant (messages are from V8/Node)js
// JSONC / JSON5 — comment is a syntax error
JSON.parse('{ "a": 1 /* note */ }');
// SyntaxError: Expected ',' or '}' after property value in JSON at position 9

// JSON5 — unquoted key and single quotes are errors
JSON.parse("{ name: 'x' }");
// SyntaxError: Expected property name or '}' in JSON at position 2

// NDJSON — only the first value parses; the rest is trailing garbage
JSON.parse('{"id":1}\n{"id":2}');
// SyntaxError: Unexpected non-whitespace character after JSON at position 9

This is not a bug to work around — it is the whole point of strict JSON. Interoperability depends on every parser agreeing on the grammar. The variants trade that universality for ergonomics or streaming, and they expect you to use a matching parser (json5, jsonc-parser, a line reader) rather than the built-in one. The exact wording of a SyntaxError differs across engines and versions, but the cause is the same. If you ever see a mysterious "Unexpected token" or "Expected" error, the first thing to check is whether you are handing a variant to a strict parser. Our guide to fixing JSON syntax errors walks through the most common cases.

Choosing the Right Variant

The decision is usually straightforward once you name the actual need:

  • You want comments in a config file that tooling reads (tsconfig, editor settings): use JSONC. It is what those tools already expect and it stays visually close to JSON.
  • You want a rich, hand-written config that feels like a JS object (unquoted keys, single quotes, hex): use JSON5, and add a matching parser to your build.
  • You are writing logs, event streams, or append-only data: use NDJSON so records stream and append without rewriting the file.
  • You are sending data over an API, storing a payload, or need maximum compatibility: use strict JSON. No exceptions — this is the only form every consumer is guaranteed to accept.

If comments are the main thing you miss and the file is config rather than data, it is worth asking whether YAML fits better — it supports comments natively and is common for CI and infrastructure config. For everything on the wire, convert your variant back to canonical JSON and validate it before it leaves your machine.

JSON ValidatorOpen the tool

Working With Variants in Practice

A few habits keep variants from causing surprises. First, name files by their dialect — .json5, .jsonc, and .ndjson (or .jsonl) signal intent to editors, linters, and teammates. Second, keep a clear boundary: variants live in your source tree and tooling; the moment data crosses a network or database boundary, it should be strict JSON. Third, automate the conversion so it is not a manual step you forget.

Converting is usually a parse-then-restringify: read with the variant's parser, write with JSON.stringify. For NDJSON, that means joining minified lines into an array or splitting an array into lines.

Converting NDJSON to a strict JSON array and backjs
// NDJSON string -> strict JSON array
function ndjsonToArray(text) {
  return text
    .split("\n")
    .filter((line) => line.trim() !== "")
    .map((line) => JSON.parse(line));
}

// strict JSON array -> NDJSON string
function arrayToNdjson(items) {
  return items.map((item) => JSON.stringify(item)).join("\n");
}

const arr = ndjsonToArray('{"id":1}\n{"id":2}');
console.log(arr); // [ { id: 1 }, { id: 2 } ]
console.log(arrayToNdjson(arr)); // {"id":1}\n{"id":2}

Once you have strict JSON, run it through a validator to confirm it parses cleanly before shipping — a thirty-second check that catches a stray trailing comma or an unquoted key that slipped through from the variant source.

Frequently asked questions

Is JSON5 the same as JSON?

No. JSON5 is a superset of JSON — every valid JSON file is valid JSON5, but not the reverse. JSON5 adds comments, trailing commas, unquoted keys, single quotes, hex numbers, and more, none of which the standard JSON grammar allows. You need a JSON5-aware parser (like the json5 npm package) to read it; the built-in JSON.parse will reject it.

What is the difference between JSONC and JSON5?

JSONC is JSON plus comments (and, in practice, trailing commas) — it keeps keys quoted and strings double-quoted, so it looks like normal JSON with notes. JSON5 goes much further, allowing unquoted keys, single-quoted strings, hexadecimal numbers, and Infinity/NaN. Use JSONC for tooling config like tsconfig.json; use JSON5 when you want a richer, object-literal-style writing experience.

Why does JSON.parse fail on my tsconfig.json?

Because tsconfig.json is JSONC, not strict JSON — it contains comments, which the standard JSON grammar forbids. JSON.parse throws a SyntaxError on the first // or /* it hits. To read it programmatically, strip the comments first (with a JSONC parser or by formatting) or use a JSONC-aware library.

What is NDJSON used for?

NDJSON (Newline-Delimited JSON, also called JSON Lines or JSONL) puts one complete JSON value per line. It is used for logs, event streams, and append-only data because you can add a record by writing one more line, process huge files line by line without loading them fully into memory, and skip a single corrupt line without invalidating the rest. Tools like BigQuery and many logging systems consume it natively.

Can I convert JSON5, JSONC, or NDJSON to standard JSON?

Yes. Parse the variant with a matching parser, then re-serialize with JSON.stringify. For JSON5/JSONC that strips comments and normalizes syntax; for NDJSON you join the per-line values into an array (or split an array into lines). After converting, run the result through a JSON validator to confirm it is strict, valid JSON before sending or storing it.

Which JSON variant should I use for API responses?

None of them — use strict RFC 8259 JSON for anything crossing a network or storage boundary. Standard JSON is the only form every parser is guaranteed to accept. Keep JSON5, JSONC, and NDJSON to config files, streams, and internal tooling, and convert to canonical JSON before it goes over the wire.