Created
December 1, 2016 16:19
-
-
Save evansd/cf68c203aa1a9a9eb78aff1e38de5c92 to your computer and use it in GitHub Desktop.
Merge nested JSON dictionaries
This file contains hidden or 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
| #!/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