A business record contains the same JSON key twice: one value says review, the other says send. Your Python application receives a tidy dictionary with only send left in it. A later validator cannot inspect the discarded pair because the parser has already removed it.
This guide catches that ambiguity at the raw-text boundary. The small reusable parser rejects duplicate decoded keys, including nested objects and escaped spellings, before returning a record dictionary. It also applies a size limit and explicit numeric checks. It does not authorize any action or decide whether the record is true.
Reproduce the hidden change
Fictional Orbit Desk uses an AI-assisted extraction step to prepare intake records. This deliberately authored fixture models a conflicting routing field; it is not output from a tested AI model. Its two queue values are review and send. No email, CRM call or routing action is performed in this example.
Python’s JSON documentation states that the default decoder keeps the last pair when object names repeat. RFC 8259 section 4 says object names should be unique and describes differing behavior when they are not. That is interoperability guidance, not a claim that every parser must reject duplicate names. This article adopts rejection as an explicit application policy.
| Authored input case | Observed result | Why it matters |
|---|---|---|
| Duplicate queue fields with default json.loads | Only send remains | The first value is lost before schema validation |
| Same input with load_record | Duplicate-key error | No ambiguous record is returned |
| Duplicate id inside an array’s object | Duplicate-key error | Nested records receive the same check |
| Escaped spelling of the same decoded key | Duplicate-key error | Checks decoded names, not raw spelling alone |
| NaN, Infinity or −Infinity | Rejected | These constants do not enter the record |
| 1e400 as a float | Rejected as overflow | The float conversion cannot quietly yield infinity |
Put the check before dictionary conversion
The ordered-pair hook receives an object’s decoded pairs, allowing the code to reject repeated names before creating that object’s dictionary. The hook runs for nested objects too. A constant callback rejects the non-finite tokens; a separate float callback rejects overflow. These customization points are documented in Python’s JSON API.
Save this as record_json.py. It needs Python only; the executed version was Python 3.14.3. Its input is a raw text string, not an SDK’s already-parsed dictionary. The top-level-object requirement and 16,384-byte UTF-8 cap are this example’s record contract, not universal JSON rules.
"""Reject duplicate decoded keys before creating a business record dictionary."""
import json
import math
MAX_BYTES = 16_384
def unique_object(pairs):
result = {}
for key, value in pairs:
if key in result:
raise ValueError('Duplicate object key')
result[key] = value
return result
def reject_constant(token):
raise ValueError('Non-finite constant rejected')
def finite_float(token):
value = float(token)
if not math.isfinite(value):
raise ValueError('Floating-point overflow rejected')
return value
def load_record(text):
if type(text) is not str:
raise ValueError('Expected raw JSON text, not a parsed object')
if len(text) > MAX_BYTES or len(text.encode('utf-8')) > MAX_BYTES:
raise ValueError('Record exceeds 16384 UTF-8 bytes')
try:
result = json.loads(text, object_pairs_hook=unique_object,
parse_constant=reject_constant, parse_float=finite_float)
except RecursionError as exc:
raise ValueError('Record nesting exceeds this parser runtime') from exc
if type(result) is not dict:
raise ValueError('This record contract requires a top-level object')
return result
if __name__ == '__main__':
raw = '{"queue":"review","queue":"send"}'
report = {'default_parser_queue': json.loads(raw)['queue']}
try:
load_record(raw)
except ValueError as exc:
report['record_parser_error'] = str(exc)
print(json.dumps(report, indent=2))
The function neither strips Markdown fences nor searches a response for a plausible object. Malformed text and trailing material are errors. If your provider supplies a structured response envelope, identify the documented raw JSON field first. Do not scrape whichever braces happen to appear in a conversational answer.
Test the parser your application will use
Save the following as test_record_json.py. The escaped-key case deliberately retains a literal backslash-u sequence in the JSON text; copying it accurately matters.
import json
import unittest
from record_json import load_record
class RecordTests(unittest.TestCase):
def test_valid_record(self):
self.assertEqual(load_record('{"queue":"review","items":[{"id":1}]}'),
{'queue': 'review', 'items': [{'id': 1}]})
def test_default_loses_first(self):
self.assertEqual(json.loads('{"queue":"review","queue":"send"}'), {'queue': 'send'})
def test_duplicate_top(self):
with self.assertRaisesRegex(ValueError, 'Duplicate'):
load_record('{"queue":"review","queue":"send"}')
def test_duplicate_nested(self):
with self.assertRaisesRegex(ValueError, 'Duplicate'):
load_record('{"items":[{"id":1,"id":2}]}')
def test_escaped_duplicate(self):
with self.assertRaisesRegex(ValueError, 'Duplicate'):
load_record(r'{"queue":"review","qu\u0065ue":"send"}')
def test_nonfinite_constants(self):
for token in ('NaN', 'Infinity', '-Infinity'):
with self.subTest(token=token):
with self.assertRaisesRegex(ValueError, 'Non-finite'):
load_record('{"score":' + token + '}')
def test_float_overflow(self):
with self.assertRaisesRegex(ValueError, 'overflow'):
load_record('{"score":1e400}')
def test_top_object_required(self):
for text in ('[]', 'null', '1', '"review"'):
with self.subTest(text=text):
with self.assertRaises(ValueError): load_record(text)
def test_invalid_json(self):
for text in ('{"a":1,}', '{"a":1} trailing', '```json\n{}\n```'):
with self.subTest(text=text):
with self.assertRaises(ValueError): load_record(text)
def test_byte_limit(self):
with self.assertRaisesRegex(ValueError, 'bytes'):
load_record('{"note":"' + '\u00e9' * 9000 + '"}')
def test_input_type(self):
for value in ({'queue': 'send'}, b'{}', None):
with self.assertRaises(ValueError): load_record(value)
def test_case_is_distinct(self):
self.assertEqual(load_record('{"queue":"review","Queue":"send"}'),
{'queue': 'review', 'Queue': 'send'})
if __name__ == '__main__':
unittest.main()
From the folder containing both files, run:
python -X utf8 -m unittest -v test_record_json.py
python -X utf8 record_json.py
Twelve test methods passed, including the baseline default behavior, nested and escaped duplicates, all three non-finite constants, float overflow, top-level shape, malformed/trailing/fenced text, UTF-8 byte size, input type and case-sensitive key distinction. The program prints:
{
"default_parser_queue": "send",
"record_parser_error": "Duplicate object key"
}
The final HTML’s program and tests were extracted into a disposable directory and both commands were executed again. Results matched the sample. No external package, API, model or business system is needed to reproduce these cases.
Parsing, schema and permission remain separate
After parsing, validate required fields, types, allowed queue values and business constraints. Then resolve the record’s source and authority. A unique queue field containing send is still untrusted input; it cannot grant permission to send anything. Pair this boundary with schema validation before a CRM handoff and the draft-only agent handoff pattern.
If an SDK has already collapsed raw output into a dictionary, this parser cannot reconstruct a discarded pair. Inspect the provider/SDK’s actual parsing path and available raw response before claiming end-to-end duplicate protection. No specific SDK integration was tested here.
On rejection, keep the item out of the actionable queue and return a concise error to a review or bounded regeneration step. Decide privately what raw material you may retain, for how long and who can see it. Do not automatically copy entire customer payloads into public logs. This example’s error messages omit duplicate values and do not write logs or files.
Define the limits before calling it strict
This is a narrow intake policy, not a complete JSON conformance or security validator. Key comparison is exact and case-sensitive: queue and Queue remain distinct. It does not normalize Unicode, reject confusable names or enforce allowed field names. Your schema must address those business ambiguities explicitly.
Float parsing still uses binary floating-point, so finite values can lose precision or underflow. This is not an exact-money or scientific-number parser. Integer conversion retains Python’s runtime limits. The byte cap bounds accepted input length; it is not a fixed nesting-depth policy, CPU budget or sandbox. Recursion errors are converted into rejection, but adversarial nesting and every Unicode edge case were not tested.
In a deployed service, enforce the transport’s body-size limit before constructing the full text string, define resource limits for untrusted input and test the exact provider-to-parser-to-schema path. Those integrations are outside this local example. A successful parse does not establish truth, safety, provenance or authority.
Jason Ead publishes AlgoThoughts AI News and connects practical AI engineering with My Biz Heroes: websites, automation, workflows, AI tools and training. Explore Jason’s work. The business and payloads are fictional. The cover is generated conceptual art; the interior table records actual parser tests.
