-
-
Save peterwwillis/3b14de8d2c7e9f6ce8899266d8aeea6d to your computer and use it in GitHub Desktop.
Complete solution to the problem posed in this blog post: http://nvie.com/posts/modifying-deeply-nested-structures/
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
def traverse(obj, path=None, callback=None): | |
""" | |
Traverse an arbitrary Python object structure (limited to JSON data | |
types), calling a callback function for every element in the structure, | |
and inserting the return value of the callback as the new value. | |
""" | |
if path is None: | |
path = [] | |
if isinstance(obj, dict): | |
value = {k: traverse(v, path + [k], callback) | |
for k, v in obj.items()} | |
elif isinstance(obj, list): | |
value = [traverse(elem, path + [[]], callback) | |
for elem in obj] | |
else: | |
value = obj | |
if callback is None: | |
return value | |
else: | |
return callback(path, value) | |
def traverse_modify(obj, target_path, action): | |
""" | |
Traverses an arbitrary object structure and where the path matches, | |
performs the given action on the value, replacing the node with the | |
action's return value. | |
""" | |
target_path = to_path(target_path) | |
def transformer(path, value): | |
if path == target_path: | |
return action(value) | |
else: | |
return value | |
return traverse(obj, callback=transformer) | |
def to_path(path): | |
""" | |
Helper function, converting path strings into path lists. | |
>>> to_path('foo') | |
['foo'] | |
>>> to_path('foo.bar') | |
['foo', 'bar'] | |
>>> to_path('foo.bar[]') | |
['foo', 'bar', []] | |
""" | |
if isinstance(path, list): | |
return path # already in list format | |
def _iter_path(path): | |
for parts in path.split('[]'): | |
for part in parts.strip('.').split('.'): | |
if part == '': | |
continue | |
yield part | |
yield [] | |
return list(_iter_path(path))[:-1] |
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
from operator import itemgetter | |
from generic import traverse_modify | |
d = { | |
"timestamp": 1412282459, | |
"res": [ | |
{ | |
"group": "1", | |
"catlist": [ | |
{ | |
"cat": "1", | |
"start": "none", | |
"stop": "none", | |
"points": [ | |
{"point": "1", "start": "13.00", "stop": "13.35"}, | |
{"point": "2", "start": "11.00", "stop": "14.35"} | |
] | |
} | |
] | |
} | |
] | |
} | |
def sort_points(points): | |
"""Will sort a list of points.""" | |
return sorted(points, reverse=True, key=itemgetter('stop')) | |
print(traverse_modify(d, 'res[].catlist[].points', sort_points)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Modified to skip adding an empty part.
If you have a data structure
d = [ { "foo": { "bar": "value" } } ]
and try to usetraverse_modify(d, "[].foo.bar", callback)
, to_path() will create a target_path["", [], "foo", "bar"]
, but transformer() is looking for a path of[[], "foo", "bar"]
.