A single comma can make a whole JSON file unusable:
{
"name": "ada",
"roles": ["admin", "ops",],
}
# Python
json.decoder.JSONDecodeError: Expecting value: line 3 column 28 (char 46)
# Node / Chrome
SyntaxError: Unexpected token ']', ..."n", "ops",],
}" is not valid JSON
Both parsers stop at the first problem, the comma inside the array. Fix that one, and the comma before the final } fails next with Expecting property name enclosed in double quotes (Python) or Expected double-quoted property name in JSON (Node).
JSON (RFC 8259) has no trailing commas. JavaScript does, so these files usually come from config copied out of code, from templates that write item + ",", or from someone deleting the last key of an object and leaving the comma behind.
The one-liner everyone reaches for
re.sub(r",\s*([}\]])", r"\1", text)
It works on the example above. Now try it on data that contains those characters inside a string:
s = '{"note": "ends with ,}", "tags": ["a", "b",],}'
print(re.sub(r",\s*([}\]])", r"\1", s))
# {"note": "ends with }", "tags": ["a", "b"]}
The output parses, so nothing warns you, but the value of note has changed. That's worse than the original error, because now the corruption is silent.
Doing it properly: track whether you're inside a string
A comma can only be removed when it is outside a string and the next non-whitespace character closes an object or array. That means you have to know whether you're inside a string, and inside a string you have to watch for backslash escapes:
def remove_trailing_commas(text: str) -> str:
out, in_string, escaped = [], False, False
for i, ch in enumerate(text):
if in_string:
out.append(ch)
if escaped:
escaped = False
elif ch == "\\":
escaped = True
elif ch == '"':
in_string = False
continue
if ch == '"':
in_string = True
elif ch == ",":
j = i + 1
while j < len(text) and text[j].isspace():
j += 1
if j < len(text) and text[j] in "}]":
continue # drop the trailing comma
out.append(ch)
return "".join(out)
json.loads(remove_trailing_commas(s))
# {'note': 'ends with ,}', 'tags': ['a', 'b']}
Where to stop
It's tempting to keep going and "fix" single quotes, unquoted keys, comments, NaN, and missing brackets too. I'd argue you shouldn't. Each of those has more than one plausible repair, and a tool that silently picks one produces a file that parses but no longer means what it did. A trailing comma is different, because there is exactly one correct fix.
So the rule I use is to repair only what has a single defensible answer, and to fail loudly with a line and column for everything else.
Disclosure: I built a small free tool around this rule, JSON Repair. It removes trailing commas outside strings and strips a leading BOM. It refuses anything ambiguous. The full guide is here.
Top comments (0)