how to convert a nested OrderedDict to dict?
This should work:
import collections
def deep_convert_dict(layer):
to_ret = layer
if isinstance(layer, collections.OrderedDict):
to_ret = dict(layer)
try:
for key, value in to_ret.items():
to_ret[key] = deep_convert_dict(value)
except AttributeError:
pass
return to_ret
Although, as jonrsharpe mentioned, there's probably no reason to do this -- an OrderedDict
(by design) works wherever a dict
does.
You should leverage Python's builtin copy
mechanism.
You can override copying behavior for OrderedDict
via Python's copyreg
module (also used by pickle
). Then you can use Python's builtin copy.deepcopy()
function to perform the conversion.
import copy
import copyreg
from collections import OrderedDict
def convert_nested_ordered_dict(x):
"""
Perform a deep copy of the given object, but convert
all internal OrderedDicts to plain dicts along the way.
Args:
x: Any pickleable object
Returns:
A copy of the input, in which all OrderedDicts contained
anywhere in the input (as iterable items or attributes, etc.)
have been converted to plain dicts.
"""
# Temporarily install a custom pickling function
# (used by deepcopy) to convert OrderedDict to dict.
orig_pickler = copyreg.dispatch_table.get(OrderedDict, None)
copyreg.pickle(
OrderedDict,
lambda d: (dict, ([*d.items()],))
)
try:
return copy.deepcopy(x)
finally:
# Restore the original OrderedDict pickling function (if any)
del copyreg.dispatch_table[OrderedDict]
if orig_pickler:
copyreg.dispatch_table[OrderedDict] = orig_pickler
Merely by using Python's builtin copying infrastructure, this solution is superior to all other answers presented here, in the following ways:
Works for more than just JSON data.
Does not require you to implement special logic for each possible element type (e.g.
list
,tuple
, etc.)deepcopy()
will properly handle duplicate references within the collection:x = [1,2,3] d = {'a': x, 'b': x} assert d['a'] is d['b'] d2 = copy.deepcopy(d) assert d2['a'] is d2['b']
Since our solution is based on
deepcopy()
we'll have the same advantage.This solution also converts attributes that happen to be
OrderedDict
, not only collection elements:class C: def __init__(self, a): self.a = a def __repr__(self): return f"C(a={self.a})" c = C(OrderedDict([(1, 'one'), (2, 'two')])) print("original: ", c) print("converted:", convert_nested_ordered_dict(c))
original: C(a=OrderedDict([(1, 'one'), (2, 'two')])) converted: C(a={1: 'one', 2: 'two'})
Simplest solution is to use json dumps and loads
from json import loads, dumps
from collections import OrderedDict
def to_dict(input_ordered_dict):
return loads(dumps(input_ordered_dict))
NOTE: The above code will work for dictionaries that are known to json as serializable objects. The list of default object types can be found here
So, this should be enough if the ordered dictionary do not contain special values.
EDIT: Based on the comments, let us improve the above code. Let us say, the input_ordered_dict
might contain custom class objects that cannot be serialized by json by default.
In that scenario, we should use the default
parameter of json.dumps
with a custom serializer of ours.
(eg):
from collections import OrderedDict as odict
from json import loads, dumps
class Name(object):
def __init__(self, name):
name = name.split(" ", 1)
self.first_name = name[0]
self.last_name = name[-1]
a = odict()
a["thiru"] = Name("Mr Thiru")
a["wife"] = Name("Mrs Thiru")
a["type"] = "test" # This is by default serializable
def custom_serializer(obj):
if isinstance(obj, Name):
return obj.__dict__
b = dumps(a)
# Produces TypeError, as the Name objects are not serializable
b = dumps(a, default=custom_serializer)
# Produces desired output
This example can be extended further to a lot bigger scope. We can even add filters or modify the value to our necessity. Just add an else part to the custom_serializer
function
def custom_serializer(obj):
if isinstance(obj, Name):
return obj.__dict__
else:
# Will get into this if the value is not serializable by default
# and is not a Name class object
return None
The function that is given at the top, in case of custom serializers, should be:
from json import loads, dumps
from collections import OrderedDict
def custom_serializer(obj):
if isinstance(obj, Name):
return obj.__dict__
else:
# Will get into this if the value is not serializable by default
# and is also not a Name class object
return None
def to_dict(input_ordered_dict):
return loads(dumps(input_ordered_dict, default=custom_serializer))