Common JSON Formatting Mistakes: Syntax Errors, Silent Data Loss and How to Fix Each One

JSON syntax is small and strict, which is why the same handful of mistakes causes most failures. Some break the parser immediately, and the error message points roughly at the problem. The more expensive ones parse fine and quietly change the data: a large ID rounded, a duplicate key overwritten, a date turned into a string, a field that disappears. Both kinds are below, with the message you will see for the first and the symptom to look for in the second.

Syntax errors the parser catches

These come from writing JSON by hand, or from treating a JavaScript object literal as JSON. The messages are from V8, which Chrome and Node.js use.

{"a": 1,}          trailing comma
                   -> Expected double-quoted property name in JSON at position 8
{a: 1}             unquoted key
                   -> Expected property name or '}' in JSON at position 1
{'a': 'x'}         single quotes: only double quotes are valid
{"a": 01}          leading zero in a number
{"a": .5}          number must start with a digit: 0.5
{"a": NaN}         NaN, Infinity and undefined do not exist in JSON
{"a": "line1
line2"}            raw newline inside a string: must be \n
// comment         comments are not part of JSON

The position in the error is a character offset into the text. The mistake is at that position or just before it. A missing comma between two properties is usually reported at the start of the second property, not where the comma should have been.

If the file is meant to allow comments and trailing commas, as tsconfig.json, VS Code settings and many configuration files do, it is JSONC or JSON5, not JSON. Parse it with a JSONC or JSON5 parser instead of fighting JSON.parse.

Strings and escaping

Inside a string, the characters " and \ must be escaped, and so must every control character below U+0020, including tab and newline. Valid escapes are \", \\, \/, \b, \f, \n, \r, \t and \uXXXX. Anything else, such as \x41, \' or \a, is an error.

Windows paths are a frequent source: "C:\new\table" is valid JSON, but it means C:, a newline, ew, a tab and able. The file path is silently wrong. Write "C:\\new\\table", or use forward slashes, which Windows APIs accept.

Characters outside the Basic Multilingual Plane, such as emoji, are written as surrogate pairs when escaped: "\ud83d\ude00". A lone surrogate is technically accepted by many parsers but is not valid Unicode and breaks some consumers.

Never build JSON by concatenating strings. '{"name": "' + name + '"}' breaks as soon as a name contains a quote, and in HTML contexts it becomes an injection bug. Use the language's serialiser.

Numbers that change without an error

JSON puts no limit on number size or precision, but most parsers map numbers to 64-bit floats. In JavaScript, integers above Number.MAX_SAFE_INTEGER (9007199254740991) are rounded:

JSON.parse('{"id": 12345678901234567890}').id
// 12345678901234567000   <- a different ID, no error

This is why APIs with 64-bit IDs, such as snowflake IDs and database BIGINT keys, send them as strings too, like Twitter/X's id_str. Send large identifiers and money amounts as strings, or parse with a big-number-aware reader such as JSON.parse's reviver with the source-text access proposal, json-bigint, or Python's default json, which keeps arbitrary-size integers.

Decimals have the same issue: 0.1 + 0.2 in a float is not 0.3. For currency, send integer minor units ("amountCents": 1999) or decimal strings ("amount": "19.99").

Duplicate keys

RFC 8259 says object names SHOULD be unique but does not forbid duplicates, and parsers disagree about what to do with them. JavaScript, Python and PHP keep the last value; some parsers keep the first or reject the document.

{"role": "user", "role": "admin"}
JSON.parse(...)  ->  { role: "admin" }

That is a security issue when two components read the same document, for example a validator that sees the first role and an application that uses the last. Merging objects by hand, or templating JSON, is how duplicates usually appear. Linters and strict parsers, such as Python with an object_pairs_hook that raises, can reject them.

What JSON.stringify silently drops or changes

JSON.stringify({ a: undefined, b: () => 1, c: NaN, d: new Date(0), e: [undefined] })
// '{"c":null,"d":"1970-01-01T00:00:00.000Z","e":[null]}'
//   a and b vanished; NaN became null; the Date became a string; undefined in an array became null

const o = {}; o.self = o;
JSON.stringify(o);   // TypeError: Converting circular structure to JSON

undefined properties, functions and symbols disappear, so an optional field is missing, not null, and the receiver cannot tell "not set" from "not sent". NaN and Infinity become null. Date objects become ISO strings and come back from JSON.parse as strings, not dates, so revive them explicitly. Map and Set become {}. BigInt throws a TypeError unless you define toJSON.

Be explicit about absent values in API contracts: send null when the field is known to be empty, and omit it when it was not requested.

File-level problems

A UTF-8 byte-order mark (EF BB BF) at the start of a file, which some Windows editors add, makes strict parsers fail at position 0 with an "unexpected token" error. Save as UTF-8 without BOM, or strip it before parsing.

JSON must be UTF-8 when exchanged between systems (RFC 8259). A file saved as UTF-16 or Latin-1 fails, or parses with garbled strings.

Several JSON documents in one file ({…}{…}) is not valid JSON. It is JSON Lines when each document is on its own line, and it needs a line-by-line reader. Conversely, a top-level array cannot be streamed and appended to as easily as JSON Lines.

A quick fixing workflow

Paste the text into a JSON formatter or validator to get a line and column rather than a character offset. Fix the first reported error only, then validate again, because one missing quote can produce a cascade of misleading errors further down. For data problems, where the JSON is valid but the values are wrong, compare the raw text with the parsed result: look at large numbers, duplicate keys and missing fields first.

jq . data.json           # validate and pretty-print; reports line and column on error
python3 -m json.tool data.json

Sources and further reading

RFC 8259 is the JSON specification, including string escapes, number grammar, duplicate names and the UTF-8 requirement. ECMA-404 is the equivalent Ecma standard. MDN documents JSON.parse, JSON.stringify and Number.MAX_SAFE_INTEGER. The JSON5 and JSONC (VS Code) documentation describe the relaxed formats.

Frequently asked questions

Why is my JSON invalid?

The most common causes are a trailing comma, single quotes, unquoted keys, comments, `NaN` or `undefined`, and unescaped newlines or backslashes inside strings. A validator shows the line and column of the first error.

Does JSON allow trailing commas?

No. A comma after the last element of an object or array is a syntax error in JSON. JSON5 and JSONC allow it, but standard parsers such as `JSON.parse` do not.

Can JSON have comments?

No. Comments are not part of JSON. Files that need them usually use JSONC or JSON5 and must be read with a parser for that format.

Why did my large number change after JSON.parse?

JavaScript stores numbers as 64-bit floats, exact only up to 9007199254740991. Larger integers are rounded without an error. Send big IDs as strings or use a big-number-aware parser.

What happens with duplicate keys in JSON?

The specification discourages them but does not forbid them. JavaScript, Python and PHP keep the last value, and other parsers may keep the first or reject the document, which can cause security bugs.

Why do dates come back as strings from JSON?

JSON has no date type. `JSON.stringify` turns `Date` objects into ISO 8601 strings, and `JSON.parse` returns them as strings. Convert them back explicitly, for example with a reviver function.