Skip to content

JSON syntax errors: the 10 most common and how to fix them

By · JSON Tools · 7 min read · Updated

JSON has one of the smallest grammars of any data format, which is exactly why it is so unforgiving. A parser either accepts a document completely or rejects it, and the error message only tells you where it gave up, not what you did wrong. After years of reading these messages in logs, the same ten mistakes account for nearly all of them. This guide shows each one, the message a modern parser prints for it, and the fix.

The messages below are from V8, the engine in Chrome and Node.js, as of 2026. Firefox and Safari word things differently, but the positions mean the same thing.

Read the position, then look backwards

Modern V8 reports a character offset plus a line and column:

SyntaxError: Expected double-quoted property name in JSON at position 35 (line 4 column 1)

The position is where the parser noticed the problem, which is often one token after the actual mistake. A trailing comma is reported at the closing brace that follows it; a missing comma is reported at the key after it. So jump to the reported line and look backwards. When the payload is minified onto one line, a small helper saves a lot of counting:

function parseOrExplain(text) {
  try {
    return JSON.parse(text);
  } catch (e) {
    const m = /line (\d+) column (\d+)/.exec(e.message);
    if (m) {
      const line = text.split('\n')[m[1] - 1];
      console.error(`${e.message}\n${line}\n${' '.repeat(m[2] - 1)}^`);
    }
    throw e;
  }
}

Or paste the document into the JSON Formatter, which shows the line and column and highlights the area.

1. Trailing commas

{
  "name": "api",
  "port": 8080,
}

Message: Expected double-quoted property name in JSON at position 35 (line 4 column 1). In an array, [1, 2,] gives Unexpected token ']'.

After a comma the parser expects another key and finds }. Delete the last comma. This is the most common error because JavaScript literals allow trailing commas, and so do "JSON with comments" files like VS Code settings and tsconfig.json, which trains the habit.

2. Single quotes

{'id': 1} gives Expected property name or '}' in JSON at position 1. Strings and keys must use double quotes. You usually meet this when someone logs a Python dict with print(data) or str(data) instead of json.dumps(data), or copies an object out of a debugger. Fix it at the source by serialising properly. Search-and-replace on quotes breaks the moment a value contains an apostrophe, such as O'Brien.

3. Unquoted keys

{id: 1} fails with the same Expected property name or '}' message. Every key is a string and needs double quotes, even simple identifiers. This one usually comes from hand-written test fixtures or a JavaScript object pasted into a config file.

4. Comments

{
  // port the API listens on
  "port": 8080
}

Message: Expected property name or '}' in JSON at position 4 (line 2 column 3). Standard JSON has no comments, neither // nor /* */. If humans edit the file and need to explain settings, use YAML or TOML and convert to JSON for the program (the YAML ⇄ JSON converter does this), or add a field like "_comment" that the application ignores.

5. NaN, Infinity and undefined

{"ratio": NaN} gives Unexpected token 'N', "{"ratio": NaN}" is not valid JSON. JSON values are strings, numbers, objects, arrays, true, false and null, nothing else. JSON.stringify already protects you here:

JSON.stringify({ a: NaN, b: Infinity, c: undefined, d: [undefined] })
// '{"a":null,"b":null,"d":[null]}'

Note what happened: NaN silently became null, and the undefined property disappeared. These values reach the wire only through template strings or non-JavaScript serialisers. Python's json.dumps writes NaN and Infinity by default; pass allow_nan=False to make it raise instead of producing JSON other parsers reject.

6. Numbers in the wrong format

InputMessageWrite instead
007Unexpected number7, or "007" if the zeros matter
+5Unexpected token '+'5
.5Unexpected token '.'0.5
5.Unterminated fractional number5.0 or 5
0xFFExpected ',' or '}' after property value255

Exponents such as 1e3 are valid. The bigger trap is not a syntax error at all: JavaScript parses every number as a 64-bit float, so JSON.parse('{"id": 9007199254740993}').id returns 9007199254740992 without a warning. Send 64-bit IDs as strings. If you control the serialiser and have BigInt values, a replacer does it: JSON.stringify(obj, (k, v) => typeof v === 'bigint' ? v.toString() : v).

7. Unescaped characters in strings

A raw line break inside a string gives Bad control character in string literal. Quotes must be written \", backslashes \\, and newlines and tabs \n and \t.

Windows paths deserve special mention because they fail in two different ways. "C:\Users\dev" fails loudly with Bad escaped character, because \U is not a valid escape. "C:\temp\new" is worse: it parses successfully, because \t and \n are valid escapes, and you get a string containing a tab and a newline. Both come from building JSON with string concatenation. A serialiser escapes correctly:

JSON.stringify({ path: 'C:\\temp\\new' })
// '{"path":"C:\\\\temp\\\\new"}'

8. Missing commas and brackets

{
  "a": 1
  "b": 2
}

Message: Expected ',' or '}' after property value in JSON at position 13 (line 3 column 3), pointing at "b", the key after the missing comma. A mismatched bracket such as {"a": [1, 2} gives Expected ',' or ']' after array element. Input that just stops, typically a truncated response or a log line cut at a size limit, gives Unexpected end of JSON input. For anything longer than a screen, format it with indentation; after a missing bracket, the indentation of everything that follows looks wrong.

9. Invisible characters

Sometimes the document looks perfect and still fails at position 0 or at a spot where nothing seems wrong. Usual suspects:

  • Byte order mark. A BOM saved by some Windows editors gives Unexpected token '', with what looks like an empty quote. Strip it with text.replace(/^\uFEFF/, '') or save as UTF-8 without BOM. PowerShell 5's Out-File and > redirection write UTF-16 with a BOM by default, which catches people generating JSON in scripts.
  • Smart quotes. {“name”: "x"} pasted from a document or chat gives Expected property name or '}'. The curly quotes look like quotes but are different characters.
  • Non-breaking spaces copied from web pages, which are not JSON whitespace and give Unexpected token ' '.

10. It was never JSON

The single most common "JSON error" in production code is not a JSON problem. These messages mean you parsed something else:

  • Unexpected token '<', "<!DOCTYPE "... is not valid JSON: the server returned HTML, usually a login page, a 404 page or a proxy error page.
  • Unexpected end of JSON input on an empty string: the response had no body, often a 204, or a 500 with an empty body.
  • "undefined" is not valid JSON: your code called JSON.parse(undefined), typically reading a missing localStorage key or field. (Python's equivalent for empty or HTML input is Expecting value: line 1 column 1 (char 0).)

Check the status and content type before parsing:

const res = await fetch(url);
const type = res.headers.get('content-type') || '';
if (!res.ok || !type.includes('application/json')) {
  const body = await res.text();
  throw new Error(`${res.status} ${res.statusText}: ${body.slice(0, 200)}`);
}
const data = await res.json();

Logging the first couple of hundred characters of the unexpected body usually identifies the problem immediately. The guide to debugging HTTP requests covers the rest of that investigation, and HTTP status codes in practice explains what the status is telling you.

Duplicate keys: the error you do not get

{"a": 1, "a": 2} parses without complaint in JavaScript and becomes {"a": 2}. The specification only says keys should be unique, so parsers differ: most keep the last value, some keep the first, a few reject the document. That difference has caused real security bugs when a gateway and a backend read the same request differently. If you accept JSON from outside, reject duplicates with a strict parser or schema validation.

Preventing errors instead of fixing them

  • Produce JSON only with a serialiser such as JSON.stringify, json.dumps or Jackson. Never concatenate strings.
  • Validate JSON config files in CI, for example with node -e "JSON.parse(require('fs').readFileSync('config.json','utf8'))" or jq empty config.json, so a broken file fails the pull request instead of the deployment.
  • When parsing input from outside your system, catch the error and return a 400 with the line and column, not a generic 500.

With those habits, JSON's strictness works in your favour: if a document parses, every compliant parser in every language reads it the same way.