JSON Key Order: Why It Changes and How to Sort JSON Keys
The same data can be written with its keys in a different order in each language, and sometimes on each run. Here is where the order comes from, and the fix for each case.
JSON key order is not guaranteed, because JSON objects are unordered by definition, but a JSON file is text, and text has an order. When that order changes, diffs fill with noise, snapshot tests fail and two hashes of the same data stop matching.
The JSON Sort Keys tool fixes a file by hand. This guide explains where the order comes from in the first place, in JavaScript, Python and Java, and how to make each one produce the same output every time.
JavaScript: numeric keys always come first
JavaScript orders an object's keys by a fixed rule. Keys that look like non-negative integers, up to 4294967294, come first in ascending numeric order. All other keys follow in the order they were added. JSON.parse adds keys in the order it reads them, so a round trip can reorder a document:
JSON.stringify(JSON.parse('{"b":1,"10":2,"a":3,"2":4}'))
// '{"2":4,"10":2,"b":1,"a":3}'The consequence is easy to miss: a JavaScript object cannot hold b before 10. No sorting function changes that, because the engine applies the rule again when the object is serialised.
Python: insertion order, until a set gets involved
Since Python 3.7, dictionaries keep insertion order, and json.loads inserts keys in file order, so a load and dump round trip keeps the order of the original. The surprise comes from sets. A set has no order, and Python randomises string hashing for each process, so a set turned into a list comes out differently from run to run:
$ for seed in 1 2 3; do
> PYTHONHASHSEED=$seed python3 -c 'import json; print(json.dumps({"roles": list({"admin", "editor", "viewer"})}))'
> done
{"roles": ["viewer", "editor", "admin"]}
{"roles": ["admin", "editor", "viewer"]}
{"roles": ["viewer", "admin", "editor"]}Without PYTHONHASHSEED, every run picks its own seed, so the output can change on every deploy. Use sorted(roles) instead of list(roles). Note that sort_keys=True sorts object keys only and does nothing for lists.
Java: Map.of and HashMap promise nothing
Map.of and Set.of iterate in an order that changes each time the JVM starts. HashMap follows its internal buckets, which is neither insertion order nor alphabetical. Jackson writes a map's entries in iteration order, so the JSON changes with it. Running this twice gave two different orders:
Map<String, Integer> roles = Map.of("viewer", 3, "admin", 1, "editor", 2, "owner", 4);
System.out.println(new ObjectMapper().writeValueAsString(roles));
// first run: {"viewer":3,"admin":1,"editor":2,"owner":4}
// second run: {"owner":4,"editor":2,"admin":1,"viewer":3}Copy the map into a TreeMap for sorted keys or a LinkedHashMap for insertion order, or let Jackson sort for you. ORDER_MAP_ENTRIES_BY_KEYS sorts map entries, and SORT_PROPERTIES_ALPHABETICALLY sorts the fields of classes and records, which otherwise follow declaration order:
ObjectMapper mapper = JsonMapper.builder()
.enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS)
.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY)
.build();
mapper.writeValueAsString(roles);
// {"admin":1,"editor":2,"owner":4,"viewer":3} on every runGo needs nothing extra: its encoding/json package writes map keys in sorted order and struct fields in declaration order.
Sorted output still differs between tools
Sorting fixes run-to-run changes, but different tools sort differently. Python and jq compare keys as strings, so 10 comes before 2. JavaScript cannot follow, because of the numeric-key rule above:
Input {"b":1,"10":2,"a":3,"2":4}
Python sort_keys=True {"10": 2, "2": 4, "a": 3, "b": 1}
jq -S {"10":2,"2":4,"a":3,"b":1}
JavaScript, sorted {"2":4,"10":2,"a":3,"b":1}Most data never uses numeric-looking keys, but IDs used as keys, such as a map from user ID to settings, do. If two systems must produce byte-identical JSON, for a signature or a cache key, pick one canonical form and use it on both sides. The JSON Sort Keys page covers canonical JSON in more detail.
How to sort JSON keys in JavaScript, Python and jq
function sortKeys(value) {
if (Array.isArray(value)) return value.map(sortKeys);
if (value && typeof value === "object") {
return Object.fromEntries(
Object.keys(value).sort().map((key) => [key, sortKeys(value[key])]),
);
}
return value;
}
JSON.stringify(sortKeys(data), null, 2);json.dumps(data, sort_keys=True, indent=2)jq -S . data.jsonSort at the point where JSON leaves your system, such as a file write, a snapshot or a hash input, not throughout the code. Inside a program the order rarely matters, and sorting everything everywhere costs time for no benefit.
Related tools
Pretty-print JSON with configurable indentation and instant validation.
Compare two JSON documents and see a precise structural diff.
Strip whitespace to shrink JSON to the smallest valid payload.
Analyze JSON size, depth, key counts, and type distribution.
Related guides
Two formatters can disagree about the same JSON file. Here is how a team picks one, applies it in every editor and before every commit, and makes CI enforce it.
JSON.stringify(a) === JSON.stringify(b) fails as soon as key order differs. Here is how to check equality properly in JavaScript and Python, and how to list exactly what changed.
Whitespace in JSON is nearly free once gzip runs. Here is where JSON travels without compression, with the extra bytes measured on sample payloads.
A payload can be 19,847 characters, 20,197 bytes and 1,803 bytes on the wire, all at once. Here is which number an API limit, a database or a browser actually checks.