Skip to main content

How to Convert JSON to OrderedDict in Python

When parsing JSON data in Python, the standard json.loads() function returns a regular dictionary. However, there are situations where you need to preserve the original key order from the JSON source explicitly, such as when processing configuration files, maintaining API response structure, or working with ordered data formats.

Python's OrderedDict from the collections module remembers the insertion order of keys and provides order-specific methods, making it ideal for these scenarios. In this guide, you will learn multiple methods to parse JSON directly into an OrderedDict, understand when this approach is necessary, and see practical examples.

Why Use OrderedDict with JSON?

Do You Still Need OrderedDict?

Starting with Python 3.7+, regular dictionaries maintain insertion order by default. This means json.loads() already preserves the key order from your JSON string in practice.

However, OrderedDict is still valuable when:

  • You need to explicitly communicate in your code that key order is significant.
  • You need OrderedDict-specific methods like move_to_end() or order-aware equality comparisons.
  • You are writing code that must work with Python 3.6 or earlier.
  • You want to guarantee order as part of a data contract, not just rely on an implementation detail.

Using json.loads() with object_pairs_hook

The most common and straightforward method is to pass object_pairs_hook=OrderedDict to json.loads(). This tells the JSON parser to construct an OrderedDict instead of a regular dict for every JSON object it encounters:

import json
from collections import OrderedDict

json_string = '{"name": "Alice", "age": 30, "city": "London"}'

# Parse JSON into an OrderedDict
data = json.loads(json_string, object_pairs_hook=OrderedDict)

print(data)
print(type(data))

Output:

OrderedDict([('name', 'Alice'), ('age', 30), ('city', 'London')])
<class 'collections.OrderedDict'>

The key order from the JSON string is preserved exactly: name, then age, then city.

How object_pairs_hook Works

When the JSON parser encounters a JSON object ({}), it normally creates a Python dict from the parsed key-value pairs. The object_pairs_hook parameter overrides this behavior. Instead of building a dict, the parser passes the key-value pairs as a list of tuples (in their original order) to the callable you specify. Since the OrderedDict constructor accepts a list of tuples, this works seamlessly.

Using JSONDecoder with object_pairs_hook

You can also create a JSONDecoder instance pre-configured to use OrderedDict. This approach is useful when you need to decode multiple JSON strings with the same settings:

import json
from collections import OrderedDict

# Create a reusable decoder
decoder = json.JSONDecoder(object_pairs_hook=OrderedDict)

json_string_1 = '{"first": 1, "second": 2, "third": 3}'
json_string_2 = '{"z": 26, "a": 1, "m": 13}'

data_1 = decoder.decode(json_string_1)
data_2 = decoder.decode(json_string_2)

print(data_1)
print(data_2)

Output:

OrderedDict([('first', 1), ('second', 2), ('third', 3)])
OrderedDict([('z', 26), ('a', 1), ('m', 13)])
note

By configuring the decoder once and reusing it, you avoid repeating the object_pairs_hook parameter on every call, which keeps the code cleaner when parsing many JSON strings.

Loading JSON Files into OrderedDict

To read a JSON file directly into an OrderedDict, use json.load() (without the trailing "s") with the same object_pairs_hook parameter:

import json
from collections import OrderedDict

with open('config.json', 'r', encoding='utf-8') as f:
data = json.load(f, object_pairs_hook=OrderedDict)

print(data)

Assuming config.json contains:

{
"database": "postgresql",
"host": "localhost",
"port": 5432,
"debug": true
}

Output:

OrderedDict([('database', 'postgresql'), ('host', 'localhost'), ('port', 5432), ('debug', True)])

Handling Nested JSON Objects

The object_pairs_hook parameter applies to all JSON objects at every nesting level, not just the top-level object:

import json
from collections import OrderedDict

json_string = '''
{
"user": {
"name": "Alice",
"age": 30,
"address": {
"street": "123 Main St",
"city": "London",
"country": "UK"
}
},
"active": true
}
'''

data = json.loads(json_string, object_pairs_hook=OrderedDict)

print(f"Top level type: {type(data)}")
print(f"User type: {type(data['user'])}")
print(f"Address type: {type(data['user']['address'])}")

print("\nAddress keys in order:")
for key, value in data['user']['address'].items():
print(f" {key}: {value}")

Output:

Top level type: <class 'collections.OrderedDict'>
User type: <class 'collections.OrderedDict'>
Address type: <class 'collections.OrderedDict'>

Address keys in order:
street: 123 Main St
city: London
country: UK

Every JSON object at every depth is parsed as an OrderedDict, ensuring order is preserved throughout the entire structure.

Converting OrderedDict Back to JSON

When converting an OrderedDict back to a JSON string, json.dumps() preserves the key order automatically:

import json
from collections import OrderedDict

data = OrderedDict([
('name', 'Alice'),
('age', 30),
('skills', ['Python', 'SQL', 'ML'])
])

json_output = json.dumps(data, indent=2)
print(json_output)

Output:

{
"name": "Alice",
"age": 30,
"skills": [
"Python",
"SQL",
"ML"
]
}

The keys appear in exactly the same order as they were defined in the OrderedDict.

Practical Example: Preserving Configuration File Order

A common real-world use case is loading a configuration file, modifying a value, and saving it back without rearranging the keys. This matters for readability and for version control systems where unnecessary reordering creates noisy diffs:

import json
from collections import OrderedDict

config_json = '''
{
"app_name": "MyApp",
"version": "2.1.0",
"database": {
"engine": "postgresql",
"host": "localhost",
"port": 5432,
"name": "mydb"
},
"logging": {
"level": "INFO",
"file": "/var/log/myapp.log"
}
}
'''

# Load with order preserved
config = json.loads(config_json, object_pairs_hook=OrderedDict)

# Modify a value
config['version'] = '2.2.0'

# Save back with the original key order intact
updated_json = json.dumps(config, indent=2)
print(updated_json)

Output:

{
"app_name": "MyApp",
"version": "2.2.0",
"database": {
"engine": "postgresql",
"host": "localhost",
"port": 5432,
"name": "mydb"
},
"logging": {
"level": "INFO",
"file": "/var/log/myapp.log"
}
}

The key order is maintained throughout the entire load, modify, and save cycle. No keys are rearranged.

OrderedDict-Specific Features

Beyond preserving order, OrderedDict provides methods that regular dictionaries do not:

from collections import OrderedDict

data = OrderedDict([
('first', 1),
('second', 2),
('third', 3)
])

# Move a key to the end
data.move_to_end('first')
print(list(data.keys()))

# Move a key to the beginning
data.move_to_end('first', last=False)
print(list(data.keys()))

# Order-sensitive equality (regular dicts ignore order in comparisons)
a = OrderedDict([('x', 1), ('y', 2)])
b = OrderedDict([('y', 2), ('x', 1)])
print(f"OrderedDict equal: {a == b}")

# Regular dicts consider these equal
print(f"Regular dict equal: {dict(a) == dict(b)}")

Output:

['second', 'third', 'first']
['first', 'second', 'third']
OrderedDict equal: False
Regular dict equal: True
note

The move_to_end() method and order-sensitive equality comparisons are capabilities that regular dictionaries do not offer, making OrderedDict the right choice when key order is part of your program's logic.

Comparing Regular Dict vs OrderedDict Parsing

import json
from collections import OrderedDict

json_string = '{"z": 1, "a": 2, "m": 3}'

# Regular dict (Python 3.7+ preserves insertion order)
regular = json.loads(json_string)
print(f"Regular dict: {regular}")

# OrderedDict (explicitly ordered)
ordered = json.loads(json_string, object_pairs_hook=OrderedDict)
print(f"OrderedDict: {ordered}")

# Both preserve key order in Python 3.7+
print(f"Keys match: {list(regular.keys()) == list(ordered.keys())}")

Output:

Regular dict: {'z': 1, 'a': 2, 'm': 3}
OrderedDict: OrderedDict([('z', 1), ('a', 2), ('m', 3)])
Keys match: True

While the key order is identical in Python 3.7+, using OrderedDict makes the intent explicit and provides access to order-specific methods.

Quick Reference

MethodReusableBest For
json.loads(s, object_pairs_hook=OrderedDict)No (per call)One-off string parsing
json.load(f, object_pairs_hook=OrderedDict)No (per call)Parsing JSON files
JSONDecoder(object_pairs_hook=OrderedDict).decode(s)Yes (reuse decoder)Repeated parsing with the same settings

Conclusion

Converting JSON to an OrderedDict in Python is straightforward using the object_pairs_hook parameter available in both json.loads() and json.load(). This single parameter ensures that every JSON object at every nesting level is constructed as an OrderedDict instead of a regular dictionary.

While Python 3.7+ dictionaries preserve insertion order by default, using OrderedDict explicitly communicates that key order is meaningful in your code, provides access to order-specific methods like move_to_end(), and enables order-sensitive equality comparisons. For repeated parsing operations, creating a JSONDecoder instance with object_pairs_hook pre-configured keeps your code clean and consistent.