Why JSON forbids comments — Crockford's rationale and how JSON5, JSONC, and HJSON came from it
JSON has no comment syntax. This isn’t an oversight or an accident of age — Douglas Crockford deliberately removed it. This article unpacks the reasoning, the dialects (JSON5, JSONC, HJSON) that emerged because of it, and the modern verdict on using JSON for configuration files at all.
What the spec actually says
Both ECMA-404 and RFC 8259 define an exact set of allowed tokens, and // and /* */ are syntax errors:
{
// Not valid JSON
"name": "Alice"
} Run that through JSON.parse() and you get Unexpected token /. Conformant parsers reject comments. No optional flag, no exception.
Crockford’s reasoning
In a 2012 Google+ post (since deleted, but quoted widely), Crockford explained the choice. Roughly:
I considered allowing comments in JSON, but I removed them because I saw people were using them to hold parsing directives, a practice which would have destroyed interoperability.
If you need comments, use a minifier to strip them before handing the JSON to a parser.
The concern wasn’t aesthetic — it was a specific implementation prediction: allowing comments would have led to the bad habit of putting directives inside them, fragmenting the format. The XML processing-instruction model (<?xml-stylesheet ?>) and HTML’s IE-only conditional comments are the prior art he was avoiding.
The “I want comments in my config” tension
JSON started as a small format for moving data out of JavaScript. Its lightness made it a popular config file format, and config files want explanatory comments — “why this value, who set it, when to revisit.” That tension produced multiple JSON derivatives.
JSON5: a human-friendly superset
JSON5, from 2012 onward, extends JSON with ECMAScript 5-style syntax:
{
// comments allowed
name: 'Alice', // unquoted keys
skills: ['JavaScript', 'Python'], // trailing commas
hex: 0xff, // hex literals
multiline: 'a b' // line continuations
} - Single-line and block comments
- Unquoted object keys
- Single-quoted strings
- Trailing commas
- Hex literals
Adopted by Babel, ESLint, Stylelint as their config format.
JSON5 is a JSON superset, not subset — standard JSON parsers can’t read it. Sharing JSON5 files requires a JSON5 parser.
JSONC: Microsoft’s minimal middle ground
JSONC, the format VS Code uses for tsconfig.json etc.
- Allows
//and/* */ - Allows trailing commas
- Otherwise standard JSON
Less ambitious than JSON5 — just comments and trailing commas. Used in tsconfig.json, .vscode/settings.json, .eslintrc.json.
The wrinkle: the file extension is still .json, so feeding the file to a stock JSON parser produces an exception. JSONC sits in an awkward “not JSON5, not JSON either” middle.
HJSON: human-first quotes-optional
HJSON goes further — quotes are optional everywhere:
{
name: Alice
skills: [
JavaScript
Python
]
bio:
'''
multi-line strings
work too
'''
} - No commas required
- Strings unquoted
- Triple-single-quote multi-line literals
Closer to YAML in writing feel, but never reached YAML-level adoption.
Why didn’t they just use YAML?
For “humans writing comments in config files,” YAML is the obvious answer. Kubernetes, GitHub Actions, GitLab CI did go that direction. Babel, TypeScript, etc. picked JSON-derivatives instead. Reasons:
- JSON’s smaller surface area makes parsing simpler.
- YAML’s Norway Problem (
NOparsed asfalse) and other implicit-type pitfalls are real production hazards. - The frontend toolchain runs on Node.js, where JSON parsing is in the standard library.
JSON5 and JSONC fill the niche of “I want comments but not YAML’s complexity.”
Lineage cheat-sheet
| Format | Comments | Trailing comma | Unquoted keys | Used in |
|---|---|---|---|---|
| JSON | ✗ | ✗ | ✗ | API responses, package.json |
| JSONC | ✓ | ✓ | ✗ | tsconfig.json, VS Code settings |
| JSON5 | ✓ | ✓ | keys only | Babel, ESLint |
| HJSON | ✓ | ✓ | ✓ | a few Go tools |
| YAML | ✓ | — | ✓ | Kubernetes, GitHub Actions |
| TOML | ✓ | — | — | Cargo, pyproject.toml |
With 20 years of hindsight
Was the no-comments choice right?
- For data interchange — yes. Universal parser availability, trivial implementations,
fetch().then(r => r.json())works everywhere. - For configuration — no, the limit showed. JSON5 and JSONC proliferated to fill the gap, leaving us in a state where “the file ends in
.jsonbut stock parsers can’t read it.”
The original concern (preventing directive-in-comment dialects) was sound. The unforeseen shift was JSON’s adoption as a config language. Crockford himself has since said JSON isn’t right for config and recommends TOML or YAML for that case.
Practical guidance for new projects
- Data interchange → JSON (interop wins).
- TypeScript / Node tooling configs → JSONC (the existing tools speak it).
- Babel / ESLint family → JSON5.
- Deploy / infra → YAML (mind Norway Problem and implicit typing).
- Designing a new tool’s config format from scratch → TOML (less ambiguous, comments first-class, types explicit).
# new tool config example
[server]
host = "0.0.0.0"
port = 8080 # integer Summary
JSON’s no-comments rule was a deliberate decision to prevent dialect fragmentation through embedded directives. As an interchange format it succeeded; as a config format it underestimated demand and led to the very fragmentation it tried to prevent (JSONC/JSON5/HJSON, all with .json extensions and incompatible parsers). It’s a clean example of how the consequences of a design choice depend on use cases the designer didn’t anticipate.
To format and validate concrete JSON files, the JSON formatter tool handles the standard form.