Skip to main content

How to Convert a Nested OrderedDict to a Dictionary in Python

Before Python 3.7, dictionaries were unordered, making collections.OrderedDict essential for preserving key insertion order. Now that standard dictionaries maintain insertion order by default, OrderedDict is mostly a legacy artifact, but many libraries, parsers, and older codebases still return them.

Converting nested structures requires recursion because the built-in dict() constructor only handles the top level. In this guide, you will learn how to fully convert nested OrderedDicts to regular dictionaries, handle edge cases, and choose the right approach for your situation.

Simple Conversion for Flat Structures

For an OrderedDict with no nesting, the built-in dict() constructor is all you need:

from collections import OrderedDict

data = OrderedDict([('a', 1), ('b', 2), ('c', 3)])

result = dict(data)

print(result)
print(type(result))

Output:

{'a': 1, 'b': 2, 'c': 3}
<class 'dict'>
Limitation of dict()

The dict() constructor does not convert nested OrderedDicts. Inner values remain as OrderedDict objects:

from collections import OrderedDict

nested = OrderedDict([('user', OrderedDict([('name', 'Alice')]))])
result = dict(nested)

print(type(result)) # <class 'dict'>
print(type(result['user'])) # <class 'collections.OrderedDict'> — NOT converted!

For any structure with nesting, you need a recursive approach.

Recursive Conversion (Complete Solution)

A recursive function handles arbitrarily deep nesting, converting every OrderedDict at every level:

from collections import OrderedDict

def to_dict(obj):
"""Recursively convert all OrderedDicts to regular dicts."""
if isinstance(obj, dict):
return {k: to_dict(v) for k, v in obj.items()}
elif isinstance(obj, list):
return [to_dict(item) for item in obj]
elif isinstance(obj, tuple):
return tuple(to_dict(item) for item in obj)
else:
return obj

# Complex nested structure
data = OrderedDict([
('user', OrderedDict([
('name', 'Alice'),
('address', OrderedDict([
('city', 'NYC'),
('zip', '10001')
]))
])),
('roles', [
OrderedDict([('id', 1), ('name', 'admin')]),
OrderedDict([('id', 2), ('name', 'user')])
])
])

clean = to_dict(data)

print(clean)
print(type(clean['user']['address']))
print(type(clean['roles'][0]))

Output:

{'user': {'name': 'Alice', 'address': {'city': 'NYC', 'zip': '10001'}}, 'roles': [{'id': 1, 'name': 'admin'}, {'id': 2, 'name': 'user'}]}
<class 'dict'>
<class 'dict'>

How it works:

  1. If the object is any kind of dictionary (including OrderedDict), it creates a new regular dict with recursively converted values
  2. If the object is a list, it recursively converts each item
  3. If the object is a tuple, it recursively converts each item and wraps the result back in a tuple
  4. For all other types, the value is returned unchanged

The isinstance(obj, dict) check catches both dict and OrderedDict because OrderedDict is a subclass of dict.

JSON Round-Trip (Quick Method)

For data containing only JSON-compatible types, serializing to JSON and parsing it back is a fast shortcut:

import json
from collections import OrderedDict

data = OrderedDict([
('a', 1),
('b', OrderedDict([('c', 2), ('d', [3, 4])]))
])

clean = json.loads(json.dumps(data))

print(clean)
print(type(clean))
print(type(clean['b']))

Output:

{'a': 1, 'b': {'c': 2, 'd': [3, 4]}}
<class 'dict'>
<class 'dict'>
JSON Round-Trip Limitations

This method has side effects that may silently corrupt your data:

  • Tuples become lists: (1, 2, 3) turns into [1, 2, 3]
  • Integer keys become strings: {1: "a"} turns into {"1": "a"}
  • Non-JSON types fail: set, bytes, datetime, and custom objects raise TypeError
  • Precision loss: Very large integers or specific float values may lose precision

Only use this approach when you are certain the data contains only strings, numbers, booleans, None, lists, and dicts.

Handling Additional Data Types

For production code that may encounter datetime, Decimal, or other specialized types alongside OrderedDict, an extended conversion function provides more control:

from collections import OrderedDict
from datetime import datetime, date
from decimal import Decimal

def deep_convert(obj, convert_dates=False, convert_decimals=False):
"""
Convert OrderedDicts to dicts with optional type handling.
"""
if isinstance(obj, dict):
return {
k: deep_convert(v, convert_dates, convert_decimals)
for k, v in obj.items()
}
elif isinstance(obj, (list, tuple)):
converted = [
deep_convert(item, convert_dates, convert_decimals)
for item in obj
]
return type(obj)(converted) # Preserves list vs tuple
elif convert_dates and isinstance(obj, (datetime, date)):
return obj.isoformat()
elif convert_decimals and isinstance(obj, Decimal):
return float(obj)
else:
return obj

data = OrderedDict([
('created', datetime(2026, 1, 15)),
('amount', Decimal('99.99')),
('items', (1, 2, 3))
])

result = deep_convert(data, convert_dates=True, convert_decimals=True)
print(result)
print(type(result['items']))

Output:

{'created': '2026-01-15T00:00:00', 'amount': 99.99, 'items': (1, 2, 3)}
<class 'tuple'>
note

The type(obj)(converted) pattern preserves whether the original container was a list or a tuple, unlike the JSON approach which converts everything to lists.

Checking for OrderedDict in a Structure

Before converting, you may want to verify whether a structure actually contains any OrderedDict instances:

from collections import OrderedDict

def to_dict(obj):
"""Recursively convert all OrderedDicts to regular dicts."""
if isinstance(obj, dict):
return {k: to_dict(v) for k, v in obj.items()}
elif isinstance(obj, list):
return [to_dict(item) for item in obj]
elif isinstance(obj, tuple):
return tuple(to_dict(item) for item in obj)
else:
return obj

def has_ordered_dict(obj):
"""Check if a structure contains any OrderedDict at any depth."""
if isinstance(obj, OrderedDict):
return True
if isinstance(obj, dict):
return any(has_ordered_dict(v) for v in obj.values())
if isinstance(obj, (list, tuple)):
return any(has_ordered_dict(item) for item in obj)
return False

data = {'a': OrderedDict([('b', 1)])}
print(f"Before conversion: {has_ordered_dict(data)}")

clean = to_dict(data)
print(f"After conversion: {has_ordered_dict(clean)}")

Output:

Before conversion: True
After conversion: False

Practical Example: YAML Parser Output

Many YAML parsers return OrderedDict objects to preserve the key order from the source file. Converting to regular dicts simplifies downstream usage:

from collections import OrderedDict

# Simulating typical YAML parser output
yaml_output = OrderedDict([
('database', OrderedDict([
('host', 'localhost'),
('port', 5432),
('credentials', OrderedDict([
('user', 'admin'),
('password', 'secret')
]))
])),
('cache', OrderedDict([
('enabled', True),
('ttl', 3600)
]))
])

def to_dict(obj):
"""Recursively convert all OrderedDicts to regular dicts."""
if isinstance(obj, dict):
return {k: to_dict(v) for k, v in obj.items()}
elif isinstance(obj, list):
return [to_dict(item) for item in obj]
return obj

config = to_dict(yaml_output)

print(type(config))
print(type(config['database']['credentials']))
print(config['database']['credentials']['user'])

Output:

<class 'dict'>
<class 'dict'>
admin

Performance Comparison

import timeit
import json
from collections import OrderedDict

def to_dict(obj):
"""Recursively convert all OrderedDicts to regular dicts."""
if isinstance(obj, dict):
return {k: to_dict(v) for k, v in obj.items()}
elif isinstance(obj, list):
return [to_dict(item) for item in obj]
return obj


def create_nested(depth=3):
"""Create a deeply nested OrderedDict structure."""
if depth == 0:
return OrderedDict([('value', 1)])
return OrderedDict([
('level', depth),
('child', create_nested(depth - 1)),
('items', [create_nested(depth - 1) for _ in range(3)])
])

data = create_nested(4)

def with_recursion():
return to_dict(data)

def with_json():
return json.loads(json.dumps(data))

print(f"Recursive: {timeit.timeit(with_recursion, number=1000):.4f}s")
print(f"JSON: {timeit.timeit(with_json, number=1000):.4f}s")

Typical output:

Recursive: 0.1234s
JSON: 0.2345s
note

The recursive approach is roughly twice as fast because it avoids the overhead of serializing to a string and parsing it back. It also preserves all Python types, whereas the JSON method coerces types during serialization. :::

Method Comparison

MethodHandles NestingType SafeBest For
dict(obj)No (top level only)YesFlat OrderedDicts with no nesting
Recursive functionYes (all levels)YesProduction code, any structure
JSON round-tripYes (all levels)No (type coercion)Quick scripts with primitive types only

Conclusion

Converting nested OrderedDict structures to regular dictionaries is straightforward with the right approach. The built-in dict() constructor works for flat structures, but any nesting requires a recursive solution. The recursive function is the recommended approach for production code because it preserves all Python data types while completely removing the OrderedDict wrapper at every level. The JSON round-trip method is a convenient shortcut for quick scripts, but be aware that it silently converts tuples to lists and integer keys to strings.

Best Practice

Use the recursive to_dict() function for production code. It preserves all data types, handles arbitrary nesting depth, and is faster than the JSON alternative. Keep the function in a utility module so it can be reused wherever your codebase encounters OrderedDict values from parsers, legacy APIs, or third-party libraries.