JSON Lint / Edge cases

Duplicate keys in JSON

JSON does not forbid duplicate keys, but parsers handle them differently — and silently. If your bug is "the same JSON behaves differently in two languages", duplicate keys are a likely cause.

Paste your JSON to find duplicate keys →

What the spec actually says

From RFC 8259 §4:

"The names within an object SHOULD be unique. … When the names within an object are not unique, the behavior of software that receives such an object is unpredictable."

"SHOULD" not "MUST". The spec explicitly allows non-unique keys and explicitly refuses to define what to do with them. That decision was punted to implementations, and they disagree.

What parsers actually do

Language / libraryDefault behaviorStrict mode?
JavaScript (JSON.parse)Last winsNo
Python (json)Last winsVia object_pairs_hook
Go (encoding/json)Last winsNo
Rust (serde_json)Last wins (default)preserve_order + manual check
Java (Jackson)Last wins (default)FAIL_ON_READING_DUP_TREE_KEY
Java (Gson)Last winsNo built-in
Ruby (json)Last winsNo
PHP (json_decode)Last winsNo

Almost universal "last wins". The exceptions are strict-mode flags (Jackson) and parsers that surface raw key/value pairs so you can decide for yourself.

How duplicates sneak in

Hand-edited config

Someone copy-pastes a section, edits one half, and forgets to delete the other. Common in package.json, tsconfig.json, composer.json.

{
  "scripts": {
    "build": "tsc",
    "test": "jest",
    "build": "tsc -w"   ← duplicate, last wins
  }
}

Programmatic merging without dedupe

A pipeline that concatenates JSON arrays of objects, then re-keys them by id without checking for collisions, will produce duplicates if two upstream sources have the same id.

Case-sensitivity surprises

JSON is case-sensitive: id and ID are different keys, not duplicates. Parsers happily keep both. Schema validators and ORMs often case-fold downstream and suddenly the duplicate appears at a layer the JSON parser already cleared.

Detecting duplicates in code

JSON.parse won't help — by the time you have an object, duplicates are already collapsed. You need to inspect raw pairs.

Python: object_pairs_hook

import json

def reject_dupes(pairs):
    seen = {}
    for k, v in pairs:
        if k in seen:
            raise ValueError(f"Duplicate key: {k}")
        seen[k] = v
    return seen

json.loads(text, object_pairs_hook=reject_dupes)

JavaScript: token scan

Use a streaming parser (stream-json) or walk the text yourself with a small tokenizer. The reviver passed to JSON.parse runs after deduplication, so it can't help.

Java: Jackson strict mode

ObjectMapper m = new ObjectMapper()
  .enable(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY);

This throws on the first duplicate. Off by default for backwards-compatibility.

The defensive rule

  1. Validate at ingest. Any JSON crossing a system boundary (HTTP request, file load, queue message) should pass through a duplicate-key check before deeper processing.
  2. Don't rely on "last wins". Even if your language does it today, a future version, a different serializer, or a CDN's JSON minifier might reorder keys.
  3. Use a schema (JSON Schema, Zod) that fails loudly on unexpected shapes. A duplicated key often produces a value of the wrong type, which a typed schema catches even when the parser doesn't.

Related

FAQ

Are duplicate keys legal JSON?

Technically yes. RFC 8259 §4 says names within an object SHOULD be unique, but does not require it. Behavior with duplicates is left to the implementation, which is why the same input parses differently in different languages.

Which parser keeps which value?

JavaScript's JSON.parse, Python's json (default), Go's encoding/json, and most major libraries keep the LAST occurrence — later keys overwrite earlier ones. Some libraries take the first or merge. Never rely on it; deduplicate at the source.

How do I detect duplicates if the parser silently drops them?

Pass an object_pairs_hook (Python), a reviver (JS), or use a streaming parser to inspect raw key occurrences before they're collapsed into an object. Our linter uses a token scan that flags every duplicate with line + column.

Is JSON Lines / NDJSON affected?

Each line is its own JSON value, so duplicates within a line are caught per-line. But multiple lines with the same 'id' are not duplicate keys — they're separate records. Different problem, different fix.

What about JSON Schema's uniqueItems?

uniqueItems applies to array items, not object keys. There is no JSON Schema keyword that forbids duplicate keys directly — you have to validate before parsing or use a strict parser that throws on them.

Open the linter →