The spec says no. Your settings.json has comments. Both are true.
· 10 min read
JSON has no comment token. So why does settings.json have comments? The four dialects that accept them, and why stripping them with a regex eats your URLs.

Put a // comment inside a .json file and one of three things happens. Your editor accepts it. Your parser throws Expecting ',' delimiter. Or somebody renames the file to .jsonc and the error disappears, until the next person, the next CI job, or the API that consumes it.
Only the second outcome is a spec violation, and it is the one nobody expects, because the file in question is called settings.json.
JSON did not quietly grow comments. RFC 8259 is current: the RFC Editor lists it as STD 90, obsoleting RFC 7159, with nothing superseding it. Its grammar has no comment production. Editors and libraries grew lenient modes anyway, .json stayed the filename, and the gap between what your editor renders and what your server parses became a recurring JSON bug with no widely used name.
MDN and the RFC cover the spec well, so this post keeps that short and spends its length on the gap.
Key Takeaways
- JSON has no comment token. RFC 8259's grammar defines four whitespace characters and six structural characters, and that is the complete list.
.jsonis the registered extension for JSON, so a commentedsettings.jsonasserts a conformance it does not have.- JSONC and JSON5 accept comments. HJSON encourages them. None of them replaces JSON on the wire.
- Comments and trailing commas are independent switches.
{allowTrailingComma: true}still accepts comments.- Any regex that strips
//also stripshttps://. Use a tokenizer.
Can you put comments in JSON?
No.
That is not a close call and not a matter of taste. The JSON grammar has no comment production, no option flag, and no extension point for one. A file with // in it is not JSON-with-comments; it is a different format wearing JSON's filename.
Everything below is about why that is hard to apply in practice.
Why the grammar has no comment token
Section 2 of RFC 8259 defines the entire token set: "A JSON text is a sequence of tokens. The set of tokens includes six structural characters, strings, numbers, and three literal names." The six structural characters are [, ], {, }, : and ,. Whitespace is four characters: space, tab, line feed, carriage return.
That is the complete inventory. There is no comment rule to disable, because there was never one. If you have worked through the JSON grammar already, the proof here is a subtraction rather than a discovery.
The design intent is in section 1: "JSON's design goals were for it to be minimal, portable, textual, and a subset of JavaScript." Minimal is the operative word. Section 12 adds the reason that mattered most, a security argument rather than an aesthetic one:
Since JSON's syntax is borrowed from JavaScript, it is possible to use that language's "eval()" function to parse most JSON texts... This generally constitutes an unacceptable security risk, since the text could contain executable code along with data declarations.
A format meant to be safely evaluable cannot afford a slot where arbitrary text can hide. Comments are the natural hiding place, so comments are out.
One check, because it is the first thing anyone asks: RFC 8259 has not been superseded. It is STD 90, dated December 2017, obsoleting RFC 7159. The draft-ietf-json-rfc4627bis in a datatracker listing looks like a pending rewrite and is not. That draft became RFC 7158 in 2013, and the line ended there.
But your settings.json has comments in it
It starts with the IANA registration in section 11 of the same RFC:
Additional information: Magic number(s): n/a File extension(s): .json
A file called settings.json is not neutrally named. The extension is a registered assertion that the contents are JSON, and a commented file breaks that assertion without breaking the filename.
Now look at what your editor does with it. From the VS Code JSON language docs:
In addition to the default JSON mode following the JSON specification, VS Code also has a JSON with Comments (jsonc) mode. This mode is used for the VS Code configuration files such as
settings.json,tasks.json, orlaunch.json. When in the JSON with Comments mode, you can use single line (//) as well as block comments (/* */) as used in JavaScript. The mode also accepts trailing commas, but they are discouraged and the editor will display a warning.
So settings.json is parsed as JSONC. The name says JSON; the parser ignores the name. The same holds for tsconfig.json, routinely written with comments and trailing commas and accepted in that form by the TypeScript compiler. That describes observed behaviour rather than quoting documentation: the TSConfig reference pages are example-only and do not state it in prose.
VS Code is not broken here, and .vscode/settings.json is not an invalid file. It is a legitimate JSONC file read by a JSONC-aware editor. The problem is one layer down: a stricter consumer of the same bytes (a server-side JSON.parse, a schema validator, a CI lint step) has no reason to be lenient.
A .json file is not necessarily JSON
The myth worth killing is the implicit one: that a file's format is decided by its extension. The extension is a claim rather than a fact, made by whoever wrote the file, usually to themselves, usually without checking who else has to read it.
Three questions that people routinely collapse into one:
- What did the author write? Often JSONC, sometimes strict JSON with one stray comment.
- What will the most lenient consumer accept? Usually the editor, which is why it looks fine.
- What will the strictest consumer accept? JSON, and that is the one returning 400 in production.
When those answers differ you have a bug that only manifests in the consumer stricter than the one you typed in.
JSON vs JSONC vs JSON5 vs HJSON
Four dialects, one table. — means the feature is not part of that dialect.
| Feature | JSON | JSONC | JSON5 | HJSON |
|---|---|---|---|---|
// line comments |
— | yes | yes | yes |
/* */ block comments |
— | yes | yes | yes |
# comments |
— | — | — | yes |
| Trailing commas | — | opt-in flag | yes | yes (comma-free style too) |
| Unquoted keys | — | — | yes (IdentifierName) | yes |
| Single-quoted strings | — | — | yes | yes |
| Multiline strings | — | — | yes (line continuation \ + block) |
yes (''') |
Hex numbers (0xdecaf) |
— | — | yes | UNVERIFIED |
| Leading/trailing decimal point | — | — | yes | UNVERIFIED |
Infinity / NaN |
no (explicitly banned) | no | yes | UNVERIFIED |
Explicit leading + |
— | — | yes | UNVERIFIED |
| Unquoted (quoteless) strings | — | — | — | yes |
| Extra whitespace chars | — | — | yes | n/a |
Those four UNVERIFIED cells are honest: the HJSON homepage does not list its numeric extensions, so this post does not assert them. Check the HJSON syntax reference before relying on hex literals there.
JSON is the baseline: RFC 8259, six value types, no comments, no trailing commas. Section 6 bans the non-finite numbers by name rather than leaving them undefined.
JSONC is the VS Code dialect, and node-jsonc-parser is its reference implementation: "JSONC is JSON with JavaScript style comments. This node module provides a scanner and fault tolerant parser that can process JSONC but is also useful for standard JSON."
Here is the detail that trips people up: comments and trailing commas are independent flags. In parser.ts the defaults are:
namespace ParseOptionsConfigs {
export const DEFAULT = {
allowTrailingComma: false
};
}
Comments are gated separately, by a disallowComments option left unset by default. So parse(text, errors, { allowTrailingComma: true }) still accepts comments, while a commented file parses fine without the flag. Conflating the two wastes an afternoon: someone enables trailing commas to "fix" a comment error and nothing changes.
JSON5 is a strict superset. Its documentation states that property and then the limit: "It is not intended to be used for machine-to-machine communication. (Keep using JSON or other file formats for that. 🙂)" Valid JSON is always valid JSON5, which is exactly why JSON5 works as an input dialect and fails as an output one.
HJSON is built for humans to edit: "You are allowed to use comments! Encouraged, even!" Its commas are optional rather than merely tolerated, so the trailing-comma row flatters it. The real claim is that you should not need commas at all. It is not an interchange format, and unlike JSONC and JSON5 it does not round-trip to strict JSON without a conversion step that decides things for you.
Why regex comment-strippers corrupt your data
When a strict consumer meets a commented file, the tempting fix is to strip the comments first. This is where people cause real damage.
Take an ordinary config file:
{
"url": "https://example.com/api", // fetch endpoint
"note": "see //docs for details"
}
Strip the comments with the pattern most people reach for:
re.sub(r'//.*$', '', text, flags=re.M)
and you get:
{
"url": "https:
"note": "see
}
json.loads then fails with Invalid control character at: line 2 column 17.
The regex matched // inside string values, first in https://, then in //docs. It truncated both. The first is a URL, one of the most common fields in real configuration. Any stripper that does not track string state will eat them, quietly, because the output still looks like JSON until something reads it.
The fix is not a better regex. It is a tokenizer. This is the relevant dispatch from node-jsonc-parser's scanner, where a double quote is consumed as a string before the scanner ever considers /:
case CharacterCodes.doubleQuote:
pos++;
value = scanString();
return token = token = SyntaxKind.StringLiteral;
// comments
case CharacterCodes.slash: {
const start = pos - 1;
// Single-line comment
if (text.charCodeAt(pos + 1) === CharacterCodes.slash) { ... LineCommentTrivia }
// Multi-line comment
if (text.charCodeAt(pos + 1) === CharacterCodes.asterisk) { ... BlockCommentTrivia }
Once a string is a token, the // inside it is data and is never re-examined. That ordering is why every library above gets this right while every regex gets it wrong.
For completeness, a strict parser's actual error strings, reproduced locally with Python's standard library:
| Input | Result |
|---|---|
{"a":1 // c\n} |
JSONDecodeError: Expecting ',' delimiter |
{"a":1 /* c */} |
JSONDecodeError: Expecting ',' delimiter |
{"a":1,} |
JSONDecodeError: Illegal trailing comma before end of object |
{a:1} |
JSONDecodeError: Expecting property name enclosed in double quotes |
Note the third row. A trailing comma is a separate violation with its own error. That is the same independence the two flags showed earlier.
How to handle it safely, per context
Whether a comment is acceptable depends on who reads the file.
- Config read by a named tool — use that tool's own parser. VS Code files go through
jsonc-parser;tsconfig.jsongoes to the compiler as written. - Config read by a strict parser — either write strict JSON, or move the annotations somewhere the parser will not look. A
_commentkey survives JSON and is invisible to most consumers, and a$schemafield already carries intent for anything that reads schemas, which JSON Schema handles separately. - Handoff between formats — YAML's native comments are why YAML keeps winning for human-edited config. Converting YAML to JSON loses them by design rather than by bug.
- Anything crossing a network boundary — comments are a defect, not a dialect choice.
Two safety notes apply to all four. Never hand a commented file to eval or Function; RFC 8259's section 12 reasoning applies to precisely that mistake. And know what a validator validates: the JSON formatter and validator is deliberately strict and rejects JSONC, which is correct for a JSON tool and means it will flag the exact files discussed here.
When comments are the wrong answer
For machine-to-machine payloads (request bodies, response envelopes, anything inside a token) comments are not a dialect question. They are a bug, and the fix is to send valid JSON.
Annotation is a human concern. Keep it on the human side of the boundary, in the file a human edits and a machine never reads directly. The dialects that accept comments exist because the payload format should stay small, and RFC 8259's design goals say so: minimal, portable, textual.
Related Tools & Further Reading
- The JSON guide for the grammar, the six value types, and where the rest of the cluster sits.
- YAML vs JSON for why a format with native comments won the config-file argument.
- What is JSON Schema for validating data rather than parsing it.
- The JSON formatter and validator for confirming a file is strict JSON. It rejects JSONC by design.
- RFC 8259, The JavaScript Object Notation Data Interchange Format for the normative text.
- VS Code's JSON language support for the
jsoncmode andfiles.associations. - jsonc-parser for the scanner and the two independent flags.
Written by
Abhay Khant
Abhay Khant is the founder of ToolSura, a privacy-first developer tools platform. Writes about client-side architecture, AI tooling, and the open web.