IMAGE BASE64 TOOLBase64 decoder

Why Did a Key Disappear from My Base64-Decoded JSON?

Base64 did not remove the repeated member: decode to bytes and inspect that text before parsing it as an object. Python json.loads keeps the last value for a repeated name by default. Preserve the original bytes when exact recovery matters; use object_pairs_hook to retain pairs or explicitly reject duplicates before a dictionary collapses them. The name can remain present while an earlier member value disappears.

Two byte-identical roundtrips, different parsed values

On 2026-10-27, our Python 3.12.14 run restored forward.json and reversed.json exactly, each from 40 Base64 characters to 30 bytes. The forward input was {"item":"ALPHA","item":"BETA"}; its default output was {"item":"BETA"}. Reversing the two members to {"item":"BETA","item":"ALPHA"} produced {"item":"ALPHA"}. Neither input contained a trailing newline, and both retained two item members in the decoded source. The forward source SHA-256 was 2aff13a2c2ddf3a749e175bc1d12d1f68111c3581e71cff0c347726466e42f4f; its compact parsed serialization was 15 bytes with SHA-256 d04e635b65204fec6ab8e07a58cb815aff75c85ad9ec508682486ee78587d62c.

A successful byte comparison establishes restoration, not the policy of the next parser. The last-value behavior is documented by Python; it is not a new rule discovered here. Do not try to repair a duplicate object by adding Base64 padding or decoding it a second time. Choose whether the consuming application should preserve, reject or otherwise resolve repeated names.

Preserve the pairs or reject them

For the 30-byte forward fixture, object_pairs_hook=list returned [["item","ALPHA"],["item","BETA"]], preserving both values in input order. The reversed fixture returned [["item","BETA"],["item","ALPHA"]]. Our supplied rejection hook raised ValueError with the message duplicate key: item for each file. In the 16-byte single.json control, that hook accepted {"item":"ALPHA"}, and compact serialization stayed exactly 16 bytes, equal to its source. The reversed duplicate fixture instead compacted to 16 bytes from 30, whereas the forward duplicate fixture compacted to 15 from 30.

Thirty restored bytes retain two members; default parsing keeps one value; pair hook keeps both

Escaped names and nested objects

The escaped-key fixture was 35 bytes and 48 Base64 characters; its second key was written as \u0069tem in the source. Its preserved pair list contained two decoded item names, its default object was {"item":"BETA"}, and our rejection hook again reported duplicate key: item. The nested control was 38 bytes and 52 Base64 characters. It parsed to {"box":{"item":"BETA"}}, while the pair hook retained ALPHA and BETA inside box and the rejecting hook refused that nested duplicate.

Single, escaped and nested controls exercise acceptance and duplicate rejection

Reproduce the outputs

Download reproduce.py and run it in an empty directory with Python. It writes the five input files, their Base64 strings and raw-output.json. Inspect source_ascii, roundtrip_equal, ordered_pairs, reject_duplicates and compact_bytes as separate results. Saving only the parsed dictionary cannot retain a member value that the parser has already discarded.

Limits of this check

These generated ASCII fixtures test the named Python version, its JSON decoder and the supplied duplicate-checking hook. They do not measure parser performance, validate a JSON Schema or establish every language and database policy. An object_pairs_hook list is a diagnostic representation, not a byte-preserving document editor: quoting, escapes and whitespace still require the original source. A source hash and a reserialized-object hash compare different representations.

For another boundary between restored bytes and the consuming parser, compare our CSV first-header experiment.

Download the reproducible inputs

forward.jsonforward.json.b64.txtreversed.jsonreversed.json.b64.txtsingle.jsonsingle.json.b64.txtescaped.jsonescaped.json.b64.txtnested.jsonnested.json.b64.txtreproduce.pyraw-output.json