Created
January 10, 2016 23:07
-
-
Save NelsonMinar/28c5928adbe1f4502af8 to your computer and use it in GitHub Desktop.
Testing various dict wrappers for Python JSON
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Various options for a JSON type for Python. | |
My best effort at using these libraries naturally for reading and writing JSON. | |
https://nelsonslog.wordpress.com/2016/01/08/a-better-python-object-for-json/ | |
""" | |
import json, copy | |
import dotmap, attrdict, easydict, addict | |
sourceData = { | |
"num": 3.4, | |
"str": "foo", | |
"list": [ 0, 1, 2, 3 ], | |
"obj": { | |
"num": 3.4, | |
"str": "foo", | |
"list": [ 0, 1, 2, 3 ] | |
}, | |
"nested": { "several": { "levels" : { "deep": 5 } } } } | |
with open('test.json', 'w') as fp: | |
json.dump(sourceData, fp) | |
loadedData = json.load(open('test.json')) | |
def usingDict(d): | |
# Plain old Python dict requires [] syntax for reading | |
assert d["num"] | |
assert d["list"][1] | |
assert d["nested"]["several"]["levels"]["deep"] | |
# Missing elements throw exceptions with the [] operator, so use get | |
assert d.get("noObj", {}) == {} | |
assert d.get("noNum", 1) | |
assert d.get("noList", []) == [] | |
# Adding nested elements requires either setdefault() or else several lines | |
d["new"] = 5 | |
d["other"] = {} | |
d["other"]["nested"] = {} | |
d["other"]["nested"]["thing"] = 6 | |
# Serialization is easy of course | |
return json.dumps(d) | |
def usingDotMap(d): | |
# dotmap lets us use . syntax for reading | |
d = dotmap.DotMap(d) | |
assert d.num | |
assert d.list[1] | |
assert d.nested.several.levels.deep | |
# Accessing a missing element with . creates an empty DotMap() | |
assert d.noObj == {} | |
assert d.get("noNum", 1) | |
assert d.get("noList", []) == [] | |
# Creating new elements is really easy, even nested | |
d.new = 5 | |
d.other.nested.thing = 6 | |
# Have to turn the DotMap into a dict to JSON encode it | |
# Note we created a new property d.missing, value is {} | |
return json.dumps(d.toDict()) | |
def usingAttrDefault(d): | |
# Create an attrdict where missing keys default to the value None | |
d = attrdict.AttrDefault(lambda: None, d) | |
# AttrDict lets us use . syntax for reading | |
assert d.num | |
assert d.list[1] | |
assert d.nested.several.levels.deep | |
# Missing attributes are none. Note: no way to override the default with get() | |
assert d.noObj == None # bummer! | |
assert d.noNum == None | |
assert d.get("noList", []) == None # bummer! | |
# No shortcut for nested objects, have to create them all | |
d.new = 5 | |
d.other = {} | |
d.other.nested = {} | |
d.other.nested.thing = 6 | |
# No way to JSON serialize an AttrDefault?! | |
try: return json.dumps(d) | |
except: return "" | |
def usingAttrDict(d): | |
# Create an attrdict with no default for missing keys | |
d = attrdict.AttrDict(d) | |
# AttrDict lets us use . syntax for reading | |
assert d.num | |
assert d.list[1] | |
assert d.nested.several.levels.deep | |
# We're on our own to handle missing attributes | |
assert d.get("noObj", {}) == {} | |
assert d.get("noNum", 1) | |
assert d.get("noList", []) == [] | |
# Can't use dot syntax: "Recursive attribute access results in a shallow copy" | |
d.new = 5 | |
d["other"] = {} | |
d["other"]["nested"] = {} | |
d["other"]["nested"]["thing"] = 6 | |
# AttrDict is a dict, so serializing is easy | |
return json.dumps(d) | |
def usingEasyDict(d): | |
# Create the easy dict | |
d = easydict.EasyDict(d) | |
# . syntax for reading | |
assert d.num | |
assert d.list[1] | |
assert d.nested.several.levels.deep | |
# Missing elements throw exceptions with both . and [] syntax, fall back to get() | |
assert d.get("noObj", {}) == {} | |
assert d.get("noNum", 1) | |
assert d.get("noList", []) == [] | |
# Adding nested elements requires multiple lines | |
d.new = 5 | |
d.other = {} | |
d.other.nested = {} | |
d.other.nested.thing = 6 | |
# EasyDict is a dict, so serializing is easy | |
return json.dumps(d) | |
def usingAddict(d): | |
d = addict.Dict(d) | |
# . syntax for reading | |
assert d.num | |
assert d.list[1] | |
assert d.nested.several.levels.deep | |
# Missing elements create empty addict.Dict()s | |
assert d.noObj == {} | |
assert d.get("noNum", 1) | |
assert d.get("noList", []) == [] | |
# Adding nested elements is a snap | |
d.new = 5 | |
d.other.nested.thing = 6 | |
# addict.Dict is a dict, so can just serialize it. | |
# we call prune first to clean out the d.noObj we created above | |
d.prune() | |
return json.dumps(d) | |
for f in (usingDict, usingDotMap, usingAttrDefault, usingAttrDict, usingEasyDict, usingAddict): | |
d = copy.deepcopy(loadedData) | |
r = f(d) | |
print ("%-16s %4d" % (f.__name__, len(r))) |
So whats the conclusion? Which one is the best?
@rfalcon100 I tried to produce this information here: https://gist.github.com/jonatasrenan/6bdc4b68d63368a0eca7d6f06b244727
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Some alternatives not mentioned:
All three provide interesting ways to handle defaults.
Source:
https://groups.google.com/d/msg/python-ideas/qvd_SXXKFuQ/hmt0JI9SBgAJ