Reject non-string JSON mapping keys before encoding
Python’s json.dumps() does not preserve the distinction between the Python keys 1 and '1': both become the JSON member name "1". Reject non-string keys before calling the encoder whenever JSON object names are part of your data contract.
The synthetic input below contains exactly that collision. The first dumps() call produces {"1": "integer", "1": "string"}. Parsing that text yields only {'1': 'string'} because a JSON object with duplicate names is decoded with the later value in Python’s default behavior. That makes the loss visible, but it is too late to repair after encoding.
require_string_keys() is deliberately narrow: it checks only the mapping keys, reports the first offending key with repr, and returns the same mapping for normal encoding. It does not claim to validate every JSON constraint in nested data; if nested mappings are allowed, apply a recursive policy separately. The assertion verifies the preflight failure happens before a second encoding call, while the corrected all-string mapping round-trips without a collision.
Python documents that object keys are coerced to strings during encoding and that repeated JSON names are accepted with the last value retained by default. See the json module documentation. This example uses only long-standing standard-library APIs; Python 3.8+ is a practical minimum.
AI assistance disclosure: this article was drafted with AI assistance and uses a synthetic in-memory fixture.
import json
def require_string_keys(mapping):
for key in mapping:
if not isinstance(key, str):
raise TypeError(f"JSON object key must be str, got {key!r}")
return mapping
ambiguous = {1: "integer", "1": "string"}
encoded = json.dumps(ambiguous, sort_keys=False)
decoded = json.loads(encoded)
assert decoded == {"1": "string"}
try:
json.dumps(require_string_keys(ambiguous))
except TypeError as error:
print(encoded)
print(decoded)
print(error)
corrected = {"1": "string", "kind": "example"}
assert json.loads(json.dumps(require_string_keys(corrected))) == corrected
print("corrected mapping encoded")
{"1": "integer", "1": "string"}
{'1': 'string'}
JSON object key must be str, got 1
corrected mapping encoded