Skip to content

Instantly share code, notes, and snippets.

@NelsonMinar
Created January 10, 2016 23:07
Show Gist options
  • Save NelsonMinar/28c5928adbe1f4502af8 to your computer and use it in GitHub Desktop.
Save NelsonMinar/28c5928adbe1f4502af8 to your computer and use it in GitHub Desktop.
Testing various dict wrappers for Python JSON
"""
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)))
@waylonflinn
Copy link

Some alternatives not mentioned:

All three provide interesting ways to handle defaults.

Source:
https://groups.google.com/d/msg/python-ideas/qvd_SXXKFuQ/hmt0JI9SBgAJ

@rfalcon100
Copy link

So whats the conclusion? Which one is the best?

@jonatasalves-hotmart
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment