Summary

  • RFC 8259 says JSON object names should be unique because receivers disagree when they are not: some keep the last pair, some fail, and some expose every pair.
  • Once a parser has collapsed duplicate names into a map, later schema checks, logs or canonicalization can make that projection orderly but cannot reconstruct the member it discarded.
  • The defensible boundary is at intake: preserve evidence, compare names after escape processing, reject ambiguity before semantic use, and document any protocol-specific exception narrowly.

The wire carries members; the application wants a map

A JSON object arrives as characters in an order. An application usually consumes something simpler: a dictionary in which each name points to one value. Those are not identical models when a name occurs twice. The received sequence can contain both members; an ordinary dictionary needs a collision rule.

That rule may be invisible. A framework parses the body before an endpoint handler runs. A gateway extracts one field to make an access decision. A signature component reads another representation. An audit library serializes the object it received from the framework. If their parsers resolve a repeated name differently, all four components can operate correctly on different claim sets.

The operational mistake is to treat “parsed successfully” as evidence that the object had one meaning. It proves only that one parser produced one result.

What RFC 8259 does—and does not—promise

RFC 8259 is careful about the boundary. Its grammar allows an object to carry a sequence of members, and the specification says their names SHOULD be unique. That word matters. Duplicate names do not automatically make the text fail the base JSON grammar.

The interoperability warning is nevertheless direct. With unique names, receiving implementations agree on the name-to-value mappings. Without uniqueness, behavior is unpredictable. Many implementations expose only the last pair. Others report an error or refuse to parse. Some expose every pair, including duplicates. Libraries also differ over whether member ordering is visible to their callers.

This is more than a serialization curiosity. Suppose an early component authorizes the first value, a later component executes the last, and the audit path records the already-collapsed object. The record may be internally consistent and still fail to show the claim that influenced the earlier decision. No exploit story is needed to see the control failure: components did not share a data model.

I-JSON turns advice into an admission rule

RFC 7493's I-JSON profile removes that latitude. An I-JSON object MUST NOT have duplicate member names. It also defines duplication after escape processing: two spellings that decode to the same Unicode sequence are the same name. A filter that compares only the visible source spellings can therefore miss the collision it was meant to catch.

The profile's purpose is predictable processing. It lets a receiving protocol reject or disregard nonconforming input, and it allows security protocols to refuse to trust it. That is a useful separation of responsibilities. Base JSON describes a broad interchange syntax; a protocol that needs one stable claim set adopts a stricter admission contract.

The timing of that check is decisive. If the duplicate detector receives only a dictionary from a parser that already used “last wins,” there is no duplicate left to detect. The check must occur in a parser mode that reports repeated names, or in a streaming/token layer that observes every decoded member name before construction of the ordinary map.

The first parser becomes an authority

When no explicit rule exists, the first parser quietly decides which value survives. That makes a library default an authority over policy, even if its maintainers intended only a convenient data structure.

A robust intake path gives this decision a visible owner. It constrains body size and nesting before expensive work, decodes names consistently, detects collisions after escape interpretation, and rejects the object before authorization, pricing, routing or persistence consumes it. It preserves the received octets—or at least a cryptographic digest under an agreed retention policy—so an investigation can distinguish what arrived from what the application later normalized.

That evidence should remain separate from the application object. Re-serializing the parsed map is not an archival substitute. It records the survivor selected by the parser, not necessarily the complete received syntax.

JWS supplies a bounded exception, not a universal default

RFC 7515 makes the scope problem visible. For JOSE Headers, Header Parameter names must be unique. A JWS parser must either reject duplicates or use a parser that returns only the lexically last duplicate member name. The rule is explicit, tied to a particular structure and accompanied by processing requirements.

It should not be generalized into “JSON is last wins.” The text governs JOSE Header Parameters, not every JSON payload, API request or configuration file. Even inside a JWS workflow, signature verification answers whether protected bytes and key material satisfy the signature rules. It does not decide whether an application should authorize the represented action.

Protocol-specific behavior is safe only when every relevant component applies the same behavior at the same boundary. A gateway that rejects, a library that keeps the last member and a monitoring tool that preserves both still do not share semantics merely because one specification permits a bounded compatibility route.

Canonicalization cannot reverse a lossy parse

RFC 8785's JSON Canonicalization Scheme is sometimes imagined as a solvent for JSON ambiguity. Its actual order is stricter. JCS constrains input to I-JSON, requires objects not to exhibit duplicate property names, serializes primitives according to defined rules and sorts properties deterministically. The canonical bytes are useful precisely because the admitted data model is already unambiguous.

If a general parser first erases one of two repeated members and JCS later canonicalizes the resulting map, the output can be perfectly deterministic. It is deterministic about the parser's projection. It cannot attest that the original input had only one occurrence, because that evidence is gone.

For signature applications, JCS describes an ordered discipline: parse and verify I-JSON conformance, validate the ecosystem's own conventions, then verify the signature, aborting if any step fails. The sequence keeps syntactic admissibility, domain validity and cryptographic validity from being collapsed into one green light.

Design the seam, not just the parser option

The practical contract should name each representation. There is the received byte sequence; the decoded token stream; the admitted, duplicate-free object; the validated domain model; and, where needed, a canonical representation or signed envelope. Each transition needs a failure outcome and an owner.

Tests should send duplicates at different nesting levels and with names that collide only after escape processing. They should run through the real gateway, framework middleware, signature path and logging stack rather than testing a utility parser alone. A useful assertion is not merely that the endpoint returned an error. It is that no downstream side effect occurred, the rejection reason was classified, and retained evidence still corresponds to the original request.

Upgrades deserve the same attention. A parser version, runtime, gateway or serialization library can change collision behavior without changing the business schema. Cross-component conformance fixtures make such drift observable before deployment.

Sources