{"record":{"id":"46cb7b52982381b9","repo":"JuliusBrussee/caveman","slug":"duplicate","errorCode":null,"errorMessage":"duplicate","messagePattern":"duplicate","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"packages/middleware/python/caveman_middleware/_google_wire.py","lineNumber":37,"sourceCode":"            raise ValueError(\"bounded\")\n        white()\n        start = index\n        if text[index] == '\"':\n            value, index = decoder.raw_decode(text, index)\n            strings[path] = (start, index, value)\n            return value\n        if text[index] == \"{\":\n            index += 1\n            result = {}\n            white()\n            if text[index] == \"}\":\n                index += 1\n                return result\n            while True:\n                white()\n                key, index = decoder.raw_decode(text, index)\n                if type(key) is not str or key in result:\n                    raise ValueError(\"duplicate\")\n                white()\n                if text[index] != \":\":\n                    raise ValueError(\"colon\")\n                index += 1\n                result[key] = walk((*path, key), depth + 1)\n                white()\n                end = text[index]\n                index += 1\n                if end == \"}\":\n                    return result\n                if end != \",\":\n                    raise ValueError(\"comma\")\n        if text[index] == \"[\":\n            index += 1\n            result = []\n            white()\n            if text[index] == \"]\":\n                index += 1","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/middleware/python/caveman_middleware/_google_wire.py#L19-L55","documentation":"While parsing a JSON object, walk decodes each key with raw_decode and requires it to be a string and not already present in the result dict. Since Python's json module normally keeps last-duplicate silently, this parser intentionally rejects duplicate keys to preserve lossless span tracking.","triggerScenarios":"Parsing a JSON object text that contains two identical keys (e.g. {\"a\":1,\"a\":2}) or a non-string key token, through parse().","commonSituations":"Hand-edited or concatenated JSON fragments producing duplicate keys; server responses built by naive string templating; migrating from stdlib json (which tolerates duplicates) to this strict wire parser.","solutions":["Deduplicate keys in the JSON source so each object has unique key names","Pre-validate the text with json.loads(object_pairs_hook=list) and check for repeated keys","Fix the producer that emits duplicate keys (templating or merge bug)","If last-wins semantics are desired, normalize via stdlib json.loads before passing to parse"],"exampleFix":"# before\ntext = '{\"branch\": \"a\", \"branch\": \"b\"}'\nvalue = parse(text)  # ValueError: duplicate\n# after\ntext = '{\"branch\": \"b\"}'  # keep only the intended key\nvalue = parse(text)","handlingStrategy":"validation","validationCode":"import json\ndef has_no_duplicate_keys(text):\n    seen = []\n    def hook(pairs):\n        keys = [k for k, _ in pairs]\n        if len(keys) != len(set(keys)):\n            raise ValueError(\"duplicate keys\")\n        return dict(pairs)\n    json.loads(text, object_pairs_hook=hook)\n    return True","typeGuard":null,"tryCatchPattern":"try:\n    value = parse(text)\nexcept ValueError as e:\n    if str(e) == \"duplicate\":\n        raise ValueError(\"JSON object contains duplicate keys; deduplicate before parsing\") from e","preventionTips":["Never build JSON by string concatenation/templating; use json.dumps","Validate incoming JSON with object_pairs_hook to detect duplicates early","Fix upstream producers that merge configs with repeated keys","Remember stdlib json silently keeps the last duplicate — don't assume parity"],"tags":["python","json","duplicate-keys","parsing"],"backgroundTag":"json-parse-error","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}