When One JSON Key Has Two Values, Which One Reaches the Tool?

Repeated JSON keys can disappear before validation begins. Reject ambiguity at the raw-input boundary and keep reviewers and executing tools on the same accepted data.

When One JSON Key Has Two Values, Which One Reaches the Tool?
Written by
ZharfAI Team
Published
October 9, 2026
Reading time
11 minutes

An assistant proposes an order using {"quantity":2,"quantity":20}. A reviewer sees two units; the executing tool receives twenty. Both values are integers. Both pass a rule allowing quantities from one to fifty. The failure is not an out-of-range value: different components have chosen different meanings for the same input.

This is a constructed integration example, not a reported incident or a claim that every parser behaves this way. The engineering decision is where to reject repeated member names before display, approval or execution. Silently choosing the last value is a policy choice, not a neutral cleanup operation.

A JSON text and the object it produces are different evidence

The input text can contain two occurrences of a name while the resulting dictionary has room for only one value under that name. Once a decoder discards an occurrence, an ordinary validator receiving the dictionary cannot inspect what disappeared. A readable object and a successful parsing call can conceal that loss.

RFC 8259 recommends unique member names, but its grammar does not simply prohibit every repeated name. It describes receiver behavior ranging from retaining the last value to rejecting the object or exposing all pairs. Therefore, successful parsing does not establish agreement among receivers.

For tool arguments, ZharfAI recommends an explicit acceptance rule: each decoded name may occur only once within its object, including when the repeated values happen to be equal. Apply that rule to model responses, imported files and adapter output. It is a proposed application policy, not a claim about how frequently any model generates duplicates.

This is a narrower problem than general factual correctness. Rejecting a duplicated quantity does not prove that the remaining quantity matches the purchase request. It removes one source of ambiguity so subsequent checks have a well-defined object to examine. The structured-output contract guide covers those later structural, semantic and authorization checks.

Put the check before the first lossy conversion

Trace the actual receiving path: response body, text decoding, JSON parsing, object construction, validation, typed binding and tool execution. Find the first component that overwrites repeated names. A duplicate detector installed after that point is too late, even if its unit tests pass when called directly with raw text.

A provider SDK may return an already parsed object. In that case, inspect the SDK's documented behavior, version and configuration. Test the relevant path with controlled input where possible. If raw content is unavailable, do not claim that your application checked it; identify the upstream guarantee and the evidence supporting it.

At a boundary you control, use a parser facility that exposes repeated members or can reject them while decoding. Do not first build a conventional dictionary and then ask whether it contains duplicates. Re-serializing that dictionary merely creates a clean-looking representation of an earlier choice.

Keep resource controls separate. Maximum body size, nesting depth and processing cost still need limits. A duplicate-name rule is not a complete defense for arbitrarily large or malformed inputs. The owner of this boundary should be able to name both its accepted data profile and the limits enforced before expensive work begins.

Compare decoded names within each object

The spellings "quantity" and "\u0071uantity" look different in the source but decode to the same name. Searching for repeated literal strings is insufficient. A matching sequence can also appear inside a string value rather than as a member name, so counting quoted fragments is not a substitute for parsing.

RFC 7493's I-JSON profile prohibits duplicate names after escape processing. That supplies a precise rule to adopt. Enforcing this one condition does not, by itself, establish compliance with every other I-JSON requirement.

Scope the check to each object. In {"source":{"id":"A"},"target":{"id":"B"}}, the two id members belong to separate objects and are not duplicates within one object. A repeated id inside either child must still be caught. A single global set of names would reject the legitimate example while obscuring the boundary being protected.

Do not add silent language normalization to this comparison. Visually similar Persian and Arabic letters, or a name containing a half-space, raise a separate mapping question. The Persian search-normalization guide explains why matching rules depend on purpose. An executable field name should not be “helpfully” rewritten using a search engine's language rules without an explicit contract.

A small Python example exposes the right control point

The Python 3.14 JSON documentation describes last-value retention as the default for repeated names. Its object_pairs_hook receives an ordered sequence of pairs for each decoded object, allowing a check before construction of the ordinary dictionary.

The following teaching example implements only that check. It is not a complete I-JSON validator, a resource-limited parser, or a solution to every numeric and Unicode edge case.

import json

def unique_object(pairs):
    result = {}
    for name, value in pairs:
        if name in result:
            raise ValueError("duplicate JSON member")
        result[name] = value
    return result

raw = r'{"quantity":2,"\u0071uantity":20}'
assert json.loads(raw)["quantity"] == 20

try:
    json.loads(raw, object_pairs_hook=unique_object)
except ValueError:
    pass
else:
    raise AssertionError("duplicate was accepted")

separate = '{"source":{"id":"A"},"target":{"id":"B"}}'
assert json.loads(separate, object_pairs_hook=unique_object)["target"]["id"] == "B"

The error deliberately avoids echoing received values or field names. Production diagnostics can associate a rejection with a request identifier and a sanitized structural location. Copying the entire rejected response into a log can expose sensitive content; untrusted field names also need care before they enter logging systems.

Run the fixture through your real middleware as well as the function. If a framework has already collapsed the input, a second parsing step cannot restore the missing pair. A successful test of this small function establishes its behavior on the fixture, not its placement in your deployed request path.

Typed binding can introduce another collision

Exact-name uniqueness is necessary for this policy but not sufficient for every consumer. quantity and Quantity are different decoded names. A case-insensitive binder, however, may connect both to the same destination field. An alias table can create a similar collision between two otherwise distinct names.

The Go encoding/json reference distinguishes legacy v1 and v2 behavior for duplicate names and field matching. The legacy struct-binding path accepts case-insensitive matches. Do not generalize that behavior to every Go package or configuration: the precise decoder and options matter.

For a tool with a fixed field vocabulary, our recommendation is to accept the contract's exact spellings and reject unknown names. Where an older producer genuinely requires aliases, make the mapping explicit and reject simultaneous use of an alias and its canonical name. Input order should not decide which value wins.

Inspect the destination state too. Prefer a fresh request value and test custom converters, coercions and any behavior involving an already populated object. Libraries do not all have identical merge semantics. Your evidence should show the final bound arguments, after the last conversion, rather than stopping at an earlier generic map.

This produces two distinct tests: uniqueness of decoded names in the input and absence of conflicting mappings into the destination schema. Combining them under a vague “JSON valid” flag makes failures harder to diagnose and encourages incomplete fixes.

Make validation, review and execution consume the accepted version

Return to the two-unit and twenty-unit order. If validation examines one parser's object while execution reparses the original text elsewhere, the earlier result says nothing decisive about the executing arguments. Checking the allowed range twice does not help: both numbers satisfy it.

After rejecting ambiguity, create a defined internal representation. Bind type checks, business rules and the approval display to that version. The tool should consume the accepted data, not retrieve the original text and reinterpret it under different rules. When a service boundary requires serialization again, keep the receiving contract explicit and test semantic agreement across both ends.

A human approval must refer to the exact values and request version shown. Someone approving two units has not approved twenty. The human-approval design guide develops this connection between reviewed intent and executed action. Duplicate rejection supports that connection; it cannot replace permission checks or approval requirements.

Legitimate transformations remain possible. A documented unit conversion, for example, may change a value under an agreed rule. Record and test its inputs and outputs. “The same accepted version” means no component silently chooses a new meaning while retaining an approval attached to the old one, not that all transformations are forbidden.

Canonicalization cannot recover a discarded conflict

Sorting and normalizing a representation before hashing can be useful, but it does not fix the order of operations. If the object has already lost one repeated value, later processing cannot establish that the original input contained only one occurrence. A tidy serialized object is not evidence about everything that preceded it.

RFC 8785, the JSON Canonicalization Scheme, requires input without duplicate property names. JCS is therefore not permission to select a preferred value from conflicting occurrences. Implement and test its prerequisites separately if your system uses it.

A digest of the original bytes answers a different question: whether two components have the same bytes. It does not ensure that they interpret those bytes identically. In an evidence record, distinguish an original-input identifier from an accepted-representation identifier, with the transformation-policy version connecting them.

This does not require retaining every raw response indefinitely. Choose retention and access controls for the real data involved. The point is to state what an integrity check establishes, what a semantic check establishes, and which version a later decision actually used.

Reject ambiguity instead of choosing a winner

The following policy is ZharfAI's recommendation for executable tool input, not a universal rule for every JSON application. An archival viewer might retain a rejected text for authorized inspection. That is different from turning it into an instruction.

Input conditionProposed disposition
Same name, two values within one objectReject; do not execute the tool
Equivalent names expressed using different escapesTreat as a duplicate and reject
Distinct names mapped to one destination fieldReject the mapping collision or revise the adapter contract
Same name in two independent objectsNo duplicate-name violation; continue structural and semantic checks
Raw ambiguity evidence unavailable after conversionRecord the evidence limitation; do not claim a raw-input check

Do not ask a model to “clean the JSON” and treat its new response as the original proposal. Regeneration creates a new candidate that needs its own checks. Where an authorized person must choose a value, present the conflict clearly through an appropriate review path. Do not choose the last occurrence on that person's behalf.

Reject equal-valued repetitions under the strict tool contract too. An exception for equality adds a content-dependent rule and complicates the acceptance surface. When migrating an older integration, measure incompatibilities in a test or shadow path. That diagnostic phase is not permission to execute ambiguous requests in a consequential workflow.

Test from raw text to final arguments

Build a compact fixture set: conflicting repeats, equal repeats, escape-equivalent names, nested duplicates, equal names in separate objects, and case or alias collisions. The last two deliberately have different expected outcomes. Keep a valid baseline so that a new safeguard cannot appear successful merely by rejecting everything.

Pass each fixture through the actual middleware, SDK adapter, validator and tool binder. Assert acceptance status, final arguments and execution count. A rejected input must produce zero tool executions. An accepted input must produce the values the reviewer saw under the same contract. This is stronger evidence than merely asserting that no exception escaped.

Report duplicate rejections against all received requests, split by producer, interface version and receiving path. A sudden increase can come from an adapter change or incorrectly combined text, not necessarily from the model. A fall to zero after losing access to raw input is not automatically an improvement either.

Rerun these fixtures when replacing a library or enabling a compatibility option. The acceptance test is practical: ambiguity is rejected before information disappears, and the team can demonstrate which values were checked, displayed and executed for every accepted request.

Source notes — reviewed October 9, 2026

These five primary references establish format and library behavior. The order example, code, acceptance policy and tests are ZharfAI engineering analysis, not customer-incident reporting or a measurement of model error rates.

#JSON#Structured Outputs#AI Integration#Input Validation#Software Testing

Related Posts

Run this inside your own team

If you want the agents and automation in this guide working for your engineering team or business processes, start with a small pilot you can measure.