Skip to content

Instantly share code, notes, and snippets.

@evansd
Created December 1, 2016 16:19
Show Gist options
  • Select an option

  • Save evansd/cf68c203aa1a9a9eb78aff1e38de5c92 to your computer and use it in GitHub Desktop.

Select an option

Save evansd/cf68c203aa1a9a9eb78aff1e38de5c92 to your computer and use it in GitHub Desktop.
Merge nested JSON dictionaries
#!/usr/bin/env python
import collections
import json
import sys
MISSING = object()
class ConflictError(Exception): pass
def merge_files(filenames):
trees = []
for filename in filenames:
with open(filename, 'rb') as f:
tree = json.load(f, object_pairs_hook=collections.OrderedDict)
trees.append(tree)
merged = collections.OrderedDict()
for tree in trees:
for path, value in get_items(tree):
set_value(merged, path, value)
print json.dumps(merged, indent=2)
def set_value(tree, path, value):
key = path[-1]
parent_path = path[:-1]
for parent_key in parent_path:
try:
tree = tree[parent_key]
except KeyError:
new_tree = collections.OrderedDict()
tree[parent_key] = new_tree
tree = new_tree
old_value = tree.get(key, MISSING)
try:
new_value = resolve(value, old_value)
except ConflictError as exc:
raise RuntimeError('Conflict at {!r}: {}'.format(path, exc))
else:
tree[key] = new_value
def resolve(new_value, old_value):
if old_value == new_value:
return old_value
if old_value is MISSING:
return new_value
if old_value is None:
return new_value
if new_value is None:
return old_value
raise ConflictError(
"Can't resolve new value {!r} and old value {!r}".format(
new_value, old_value))
def get_items(tree, parent_path=()):
for key, value in tree.items():
path = parent_path + (key,)
if isinstance(value, collections.Mapping):
for child_path, child_value in get_items(value, parent_path=path):
yield child_path, child_value
else:
yield path, value
if __name__ == '__main__':
merge_files(sys.argv[1:])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment