Skip to content
LocalOnly
JSON Tools

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.

Updated October 18, 20263 min readTool runs in your browser

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 run

Go 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

JavaScript: sort keys recursively, numeric keys still first
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);
Python
json.dumps(data, sort_keys=True, indent=2)
jq
jq -S . data.json

Sort 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.

Frequently asked questions

Command Palette

Search for a tool or command