Common JSON Errors and How to Fix Each One
Trailing commas, single quotes, comments, NaN, escaped strings and big numbers that change: why JSON rejects each one, and the fix that works.
Computing··9 min read
JSON looks like JavaScript, which is why it breaks so often. You copy an object out of a script, a config file or a Python print statement, it looks right, and the parser stops at character 9.
The rules are short. The current standards, RFC 8259 from the IETF and ECMA-404 from Ecma International (both published in December 2017), define the same small grammar: objects, arrays, double-quoted strings, numbers, and the three words true, false and null. Anything else is an extension that some parsers accept and others don't. RFC 8259 says this outright in section 9: a parser may accept non-JSON forms. That is why a file can work in one tool and fail in the next.
Below are the errors you will actually meet, what Node and Chrome say about each one (the messages come from V8, the engine both use; they were checked in Node 26 and wording differs between engines and versions), and how to fix them.
1. Trailing commas
{
"name": "Ada",
"langs": ["en", "fr",],
}
JavaScript has allowed a comma after the last item for years, so editors and formatters add them. JSON's grammar has no place for one: a comma must be followed by another value or another key.
JSON.parse('{"a": 1,}') fails with:
Expected double-quoted property name in JSON at position 8 (line 1 column 9)
After a comma the parser expects another "key", and it found }. Fix: delete the comma before every } and ].
2. Single quotes (and curly quotes)
{'city': 'Lisbon'}
JSON strings use double quotes only, and so do keys. MDN's JSON.parse page lists single-quoted strings under "Illegal JSON". V8 reports:
Expected property name or '}' in JSON at position 1 (line 1 column 2)
A close cousin is the curly quote: paste JSON through a chat app or word processor and "city" can come back as “city”, a different character the parser rejects.
Fix: replace ' with " around keys and strings, and straighten any curly quotes. If a string contains a double quote, escape it as \". An apostrophe inside a double-quoted string is fine as it is: "it's" is valid. Writing \' is not, because JSON has no such escape.
3. Comments
{
// retry three times
"retries": 3
}
The JSON grammar has no comment syntax. Formats that look like JSON often do: JSON5 permits // and /* */ comments, trailing commas, single quotes, unquoted keys, hex numbers and NaN/Infinity. Such files are fine for the program that reads them and invalid for everything else.
V8's message for the example above is Expected property name or '}' in JSON at position 4: it hit the / where a key should be.
Fix: remove the comments. If the note matters, keep it as data, for example "_comment": "retry three times", and make sure whatever reads the file ignores that key.
4. Unquoted keys
{name: "Ada", age: 36}
Perfectly good JavaScript, not JSON. RFC 8259 section 4 says each name in an object is a string, and strings in JSON are always in double quotes. V8 gives the same "Expected property name or '}'" error as for single quotes. Fix: {"name": "Ada", "age": 36}.
5. NaN, Infinity, undefined (and Python's True and None)
{"score": NaN, "limit": Infinity, "note": undefined}
RFC 8259 section 6 states that numbers like Infinity and NaN are not permitted. undefined doesn't exist in JSON at all, and section 2 requires true, false and null to be lowercase.
These usually arrive from two places:
- Python. The standard library's
json.dumpswritesNaNandInfinityby default. The Python docs are candid that this is not spec compliant, and that passingallow_nan=Falsemakes it raise aValueErrorinstead. Printing a Python dict withprint(data)is worse: you get{'ok': True, 'next': None}, which has single quotes and capitalised literals, so it is not JSON at all. Usejson.dumps(data). - Hand-built strings in JavaScript.
JSON.stringifyitself is safe here: per MDN, it writesNaNandInfinityasnull, dropsundefinedproperties from objects, and turnsundefinedin arrays intonull. The trouble comes from template strings like`{"score": ${score}}`.
V8's message is Unexpected token 'N', "{"score": NaN}" is not valid JSON.
Fix: decide what the missing value means: usually null, or leaving the key out. Watch for the silent version too: a NaN that JSON.stringify turned into null may hide a division by zero upstream.
6. Missing commas
{"a": 1 "b": 2}
Usually from merging snippets by hand. V8 says Expected ',' or '}' after property value. The position it reports is where the parser noticed, which is the start of the next key, not the end of the previous line. Look one step to the left of the caret.
7. Escaped JSON: a string that contains JSON
This one parses fine, which is what makes it confusing:
"{\"user\":\"ada\",\"id\":42}"
It is valid JSON: a single string. You get it when data is encoded twice, for example when an API stores a JSON document in a text field and then serialises the whole response again. Parsing it gives you a string, not an object:
const raw = '"{\\"user\\":\\"ada\\",\\"id\\":42}"';
typeof JSON.parse(raw); // 'string'
JSON.parse(JSON.parse(raw)).user; // 'ada'
Fix: parse once more, then find the step that encoded it twice. The second JSON.stringify is usually on a value that was already a string. If the payload came wrapped in Base64 (JWTs and many webhook bodies do this), decode it first with a Base64 decoder, then validate.
8. Line breaks and tabs inside strings
{"address": "1 Main St
Springfield"}
RFC 8259 section 7 lists the characters that must be escaped inside a string: the double quote, the backslash, and the control characters U+0000 to U+001F. That range includes a real newline and a real tab. V8 says Bad control character in string literal.
Fix: write the escape instead of the character: "1 Main St\nSpringfield", and \t for a tab. A literal backslash, as in a Windows path, must be doubled: "C:\\Users\\ada".
9. Big numbers that quietly change
This is the one that never throws an error. Take an ID from a database that uses 64-bit integers:
JSON.parse('{"id": 9007199254740993}').id;
// 9007199254740992
The last digit changed, and nothing warned you.
JavaScript stores every Number as an IEEE 754 double. MDN's page on Number.MAX_SAFE_INTEGER explains that this gives exact integers only up to 2^53 − 1, which is 9,007,199,254,740,991. Past that, neighbouring integers can share one representation; MDN's example is that MAX_SAFE_INTEGER + 1 === MAX_SAFE_INTEGER + 2 is true.
JSON itself has no size limit on numbers. RFC 8259 section 6 instead gives an interoperability note: since software commonly uses doubles, only integers in the range −(2^53 − 1) to 2^53 − 1 are guaranteed to mean the same thing to every implementation. ECMA-404 deliberately says nothing about how numbers should be stored. So a Java or Go service can send an ID that is valid JSON and still arrives wrong in a browser.
Fixes, best first:
- Send large IDs as strings.
{"id": "9007199254740993"}survives every parser. If you control the API, this is the fix. - Read the original digits with a reviver. Newer engines pass a third
contextargument to theJSON.parsereviver, with the number's source text. This comes from the TC39 JSON.parse source text access proposal, which has reached stage 4. It works in current Node; check your target browsers before relying on it.
const data = JSON.parse('{"id": 9007199254740993}', (key, value, context) =>
key === 'id' ? BigInt(context.source) : value
);
data.id; // 9007199254740993n
- Writing them back out.
JSON.stringifythrows aTypeErroron aBigInt(MDN's stringify page documents this). The same proposal addsJSON.rawJSON, which writes the digits exactly as given:JSON.stringify({ id: JSON.rawJSON('9007199254740993') })gives{"id":9007199254740993}.
Our post on data types covers how numbers are stored in more depth.
Duplicate keys
One more silent one:
{"role": "viewer", "role": "admin"}
JavaScript parses this happily and gives you "admin". RFC 8259 says names should be unique, that behaviour otherwise is unpredictable, and that many implementations keep the last pair. If two systems disagree about which value wins, that's a bug, and in a permissions field possibly a security one. Fix: make keys unique.
Reading the error position
V8 reports a character position (from 0) plus a line and column (from 1). Two habits help:
- Format first. On one minified line, "position 48213" is useless; pretty-printed, the line number points somewhere useful.
- Look just before the caret. The parser reports where it gave up, usually just after the real mistake.
Let a validator do the tedious part
Our JSON validator checks as you type and shows the line and column of the first error in plain words. Its Fix it button handles most of the list above: trailing and missing commas, comments, single and curly quotes, unquoted keys, NaN/Infinity/undefined, Python's True/False/None, raw tabs and bad escapes in strings, and JSON stored as an escaped string. It shows what it will change before applying it, and Ctrl+Z undoes it. It won't guess at a missing bracket; it points at where the bracket should be.
It also warns about the two silent problems, long integers and duplicate keys, and its Format, Minify and Sort keys work on your text directly, so long IDs keep every digit. Everything runs in your browser: a pasted API response isn't sent anywhere.
The short version
| You see | Likely cause | Fix |
|---|---|---|
| Expected double-quoted property name | Trailing comma before } |
Delete the comma |
| Expected property name or '}' | Single quotes, unquoted key or comment | Double-quote keys, remove comments |
| Unexpected token 'N' / 'u' / 'I' | NaN, undefined, Infinity |
Use null or drop the key |
| Expected ',' or '}' after property value | Missing comma | Add it before the reported position |
| Bad control character in string literal | Real newline or tab in a string | Use \n or \t |
| Parses to a string, not an object | JSON encoded twice | Parse again, fix the double encoding |
| No error, but an ID changed | Integer above 2^53 − 1 | Send it as a string, or use a reviver with context.source |
Sources
- Bray, T., ed. (2017). RFC 8259: The JavaScript Object Notation (JSON) Data Interchange Format. IETF, STD 90. Sections 2, 4, 6, 7, 8.1 and 9.
- Ecma International (2017). ECMA-404: The JSON Data Interchange Syntax, 2nd edition.
- MDN Web Docs. JSON.parse().
- MDN Web Docs. JSON.stringify().
- MDN Web Docs. Number.MAX_SAFE_INTEGER.
- TC39. JSON.parse source text access proposal (stage 4).
- Python Software Foundation. json: JSON encoder and decoder, Python 3 documentation.
- JSON5 Data Interchange Format.
Keep reading
Computing · Jul 18, 2026 · 2 min
What LLM Tokens Really Cost (and How to Estimate Your Bill)
Tokens, context windows, input vs output pricing: how large language model costs add up, and how to estimate your monthly bill before it surprises you.
Computing · Oct 7, 2026 · 11 min
Mermaid Flowcharts and Sequence Diagrams: A Practical Guide
Write Mermaid flowcharts and sequence diagrams that render first time: shapes, arrows, subgraphs, alt and loop blocks, and the errors that break them.
Computing · Jul 18, 2026 · 2 min
DORA Metrics Explained: The Four Keys to Delivery Performance
Deployment frequency, lead time, change failure rate and time to restore: what the four DORA metrics measure, how to read them and improve them honestly.
Computing · Jul 18, 2026 · 2 min
What LLM Tokens Really Cost (and How to Estimate Your Bill)
Tokens, context windows, input vs output pricing: how large language model costs add up, and how to estimate your monthly bill before it surprises you.
Computing · Oct 7, 2026 · 11 min
Mermaid Flowcharts and Sequence Diagrams: A Practical Guide
Write Mermaid flowcharts and sequence diagrams that render first time: shapes, arrows, subgraphs, alt and loop blocks, and the errors that break them.
Computing · Jul 18, 2026 · 2 min
DORA Metrics Explained: The Four Keys to Delivery Performance
Deployment frequency, lead time, change failure rate and time to restore: what the four DORA metrics measure, how to read them and improve them honestly.