Skip to content

Instantly share code, notes, and snippets.

Forked from nvie/
Last active Jun 22, 2020
What would you like to do?
Complete solution to the problem posed in this blog post:
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]
value = obj
if callback is None:
return value
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)
return value
return traverse(obj, callback=transformer)
def to_path(path):
Helper function, converting path strings into path lists.
>>> to_path('foo')
>>> to_path('')
['foo', 'bar']
>>> to_path('[]')
['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 == '':
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))

This comment has been minimized.

Copy link
Owner Author

@peterwwillis 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, "[]", 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