Skip to content

Instantly share code, notes, and snippets.

@peterwwillis
Forked from nvie/generic.py
Last active June 22, 2020 07:32
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save peterwwillis/3b14de8d2c7e9f6ce8899266d8aeea6d to your computer and use it in GitHub Desktop.
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/
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]
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))
@peterwwillis
Copy link
Author

peterwwillis commented May 23, 2019

Modified to skip adding an empty part.

If you have a data structure d = [ { "foo": { "bar": "value" } } ] and try to use traverse_modify(d, "[].foo.bar", callback), to_path() will create a target_path ["", [], "foo", "bar"], but transformer() is looking for a path of [[], "foo", "bar"].

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