JSON Toolkit

JSON5 and JSONC: JSON with Comments

Bottom line: Both dialects exist because human-written config needs comments and trailing commas, but their syntax and ecosystem reach differ, so picking wrong jams your toolchain.

Beginner~10 min readPublished 2026-10-30Updated 2026-10-30
Cover for JSON5 and JSONC, JSON with comments

Bottom line: use JSONC for comments inside VS Code and TypeScript configuration, use JSON5 when you need the broader ES5.1 literal syntax such as single quotes or unquoted keys, and keep public APIs on standard JSON.

Standard JSON does not allow comments, so every team quietly writes them anyway — until one day the build breaks. Rather than doing it secretly, pick a proper dialect.

1. The two dialects have different motivations

  • JSONC (JSON with Comments): standard JSON plus comments and trailing commas, with almost no other syntax changes. It is the de-facto standard of the VS Code ecosystem — settings.json, tsconfig.json, and .eslintrc.json all use it.
  • JSON5: far more ambitious, aligning directly with the ES5.1 object literal — single quotes, unquoted keys, hexadecimal, Infinity/NaN, backslash line continuation, and leading + signs.

One sentence to tell them apart: JSONC only makes writing comfortable; JSON5 lets you write a configuration file as if it were JavaScript.

2. Syntax comparison table

Feature Standard JSON JSONC JSON5
Comments // /* */ ✗ ✓ ✓
Trailing commas ✗ ✓ ✓
Single-quoted strings ✗ ✗ ✓
Unquoted keys ✗ ✗ ✓
Hexadecimal numbers ✗ ✗ ✓
Leading/trailing decimal point .5 5. ✗ ✗ ✓
Numbers starting with + ✗ ✗ ✓
Infinity / NaN ✗ ✗ ✓
End-of-line \ continuation ✗ ✗ ✓
Multi-line strings ✗ ✗ ✓ (implemented through continuation)
Arbitrary top-level value ✓ ✓ ✓

The full JSON5 specification is barely more than a page; the json5 package has long been maintained by the community, and browsers and Node behave identically.

3. Parsing: two APIs, two kinds of positioning ability

3.1 JSON5: simple whole-document parsing

import JSON5 from 'json5';

const config = JSON5.parse(`
{
  // service port
  port: 8080,
  host: '0.0.0.0',
  features: ['json5', 'jsonc',],
  retries: 0x10,
}
`);

console.log(config.port, config.retries); // 8080 16

JSON5.parse returns a plain object with no offset information — because it is not aimed at editing scenarios, only at "reading configuration".

3.2 JSONC: a syntax tree with offsets

The value of jsonc-parser lies not in parsing but in editing. It offers three layers of API:

API Purpose Typical use
parse Parse into a tree carrying offset/length Syntax highlighting, folding, navigation
visit Depth-first traversal; the callback receives the node path JSONPath-like inspection
modify + applyEdits Produce a minimal text edit, then apply it to the original text Change a value while keeping comments
import { parseTree, findNodeAtLocation, modify, applyEdits } from 'jsonc-parser';

const text = `{
  // port configuration
  "server": {
    "port": 8080 // 80 in production
  }
}`;

const tree = parseTree(text);
const portNode = findNodeAtLocation(tree!, ['server', 'port']);
console.log(portNode?.offset, portNode?.length);

const edits = modify(text, ['server', 'port'], 3000, {
  formattingOptions: { insertSpaces: true, tabSize: 2 },
});
console.log(applyEdits(text, edits));

The output keeps the original comments and replaces only the three characters of 8080:

{
  // port configuration
  "server": {
    "port": 3000 // 80 in production
  }
}

That is exactly what "editing configuration with regular expressions" cannot do: a regex has no idea which 8080 to change, and no idea that the 8080 inside a string must be left alone.

3.3 Error messages must carry offsets

The error codes from jsonc-parser are enum values; the common ones are:

Error code Meaning
InvalidSymbol A character appeared that should not be there
PropertyNameExpected Expected a key name
ColonExpected Expected a colon
CommaExpected Expected a comma
CloseBraceExpected Object was not closed
UnexpectedEndOfComment Comment was not closed
InvalidEscapeCharacter Invalid escape

Once offset is converted into a line and column, the editor can draw the squiggle exactly on the offending position.

4. When to use which

Scenario Recommendation Reason
Public API request/response bodies Standard JSON Dialects are not supported by general-purpose clients
package.json, composer.json Standard JSON Package managers only understand standard JSON
tsconfig.json, .vscode/settings.json JSONC The ecosystem default, natively supported by the toolchain
Application configuration (needs comments) JSONC or YAML Comments must be preserved while tools can still read and write it
Needing Infinity, hexadecimal, complex literals JSON5 The strongest expressive power
Data exchange, logs, caches Line-delimited or standard JSON Streaming and universality are required

Rule of thumb: if the file will be read by a "non-human tool", prefer standard JSON; only consider a dialect when you are certain it is read solely by your own code.

5. Three kinds of risk that dialects introduce

5.1 Spillover of the requirement

When another tool reads a JSON5 configuration file and calls JSON.parse, it crashes outright. The typical symptom is "works fine locally, fails on Docker" — because the container runs a different reading path.

Countermeasure: add a CI check to the repository that explicitly lists which files are allowed to use a dialect; everything else is validated as standard JSON.

5.2 Surprises around type inference

JSON5 allows unquoted keys and single quotes, but it does not let you bring in literal constructs such as dates or regular expressions — it is still a data format, not JavaScript evaluation. People have assumed JSON5 could express new Date() only to see the parse fail. JSON5 is not eval.

5.3 Comments silently dropped

Neither JSON5.stringify nor JSON.stringify preserves comments. So "read it in, tweak a value, write it back" loses every comment. When comments must survive, the only options are the editing API of jsonc-parser, or a YAML library that supports comment preservation.

6. Practical recommendations

  • [ ] Keep a "file type → format" table in the team documentation so nobody improvises;
  • [ ] Wrap configuration reading in a single loadConfig() that decides internally which parser to use, so business code never calls JSON.parse directly;
  • [ ] When programmatically modifying a configuration with comments, use modify + applyEdits; never rewrite the whole file;
  • [ ] Validate dialect files in CI too (jsonc-parser for JSONC, JSON5.parse for JSON5), otherwise syntax errors only surface at runtime;
  • [ ] Use standard JSON for public interfaces, cache keys, and message-queue payloads.

FAQ

How should I choose between JSON5 and JSONC?

Choose by runtime rather than by syntax count: VS Code, TypeScript and ESLint read JSONC by default, so JSONC gives the best compatibility; reach for JSON5 only when you need the broader ES5.1 literal syntax, such as single quotes, unquoted keys or Infinity. Neither dialect is appropriate for public API responses.

Why can't a standard JSON parser read JSONC directly?

JSONC allows comments and trailing commas, while JSON.parse accepts only RFC 8259 JSON and throws on either. Reading JSONC requires a parser such as jsonc-parser, or removing comments before handing the text to a standard parser.

Should comments in JSONC survive edits to the file?

Yes, but only through an editing API. jsonc-parser's modify and applyEdits change the targeted node while preserving the remaining comments; parsing to an object and stringifying it drops every comment.