Last active
August 29, 2015 13:57
-
-
Save rbonvall/9835300 to your computer and use it in GitHub Desktop.
Compare two JSONable Python data structures and enumerate their differences
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
#!/usr/bin/env python | |
def add_to_path(path, item): | |
return '{}[{}]'.format(path, repr(item)) | |
def dictcompare(old, new, path, differences): | |
old_keys = set(old.keys()) | |
new_keys = set(new.keys()) | |
for key in old_keys - new_keys: | |
differences.append('{}: key only in old'.format(add_to_path(path, key))) | |
for key in new_keys - old_keys: | |
differences.append('{}: key only in new'.format(add_to_path(path, key))) | |
for key in old_keys & new_keys: | |
recursive_compare(old[key], new[key], add_to_path(path, key), differences) | |
def listcompare(old, new, path, differences): | |
n_old = len(old) | |
n_new = len(new) | |
if n_old != n_new: | |
differences.append('{}: list lengths differ ({} != {})'.format(path, n_old, n_new)) | |
differing_indices = [i for i, (x_old, x_new) in enumerate(zip(old, new)) if x_old != x_new] | |
for i in range(min(n_old, n_new)): | |
recursive_compare(old[i], new[i], add_to_path(path, i), differences) | |
def recursive_compare(a, b, path, differences): | |
if isinstance(a, list) and isinstance(b, list): | |
listcompare(a, b, path, differences) | |
elif isinstance(a, dict) and isinstance(b, dict): | |
dictcompare(a, b, path, differences) | |
elif type(a) != type(b): | |
differences.append('{}: types differ ({} != {})'.format(path, type(a).__name__, type(b).__name__)) | |
elif a != b: | |
differences.append('{}: {} != {}'.format(path, repr(a), repr(b))) | |
def compare(old, new, name='obj'): | |
differences = [] | |
recursive_compare(old, new, name, differences) | |
return differences | |
if __name__ == '__main__': | |
from pprint import pprint | |
from collections import namedtuple | |
TestCase = namedtuple('TestCase', 'name expected received') | |
test_cases = [ | |
TestCase('Messy structure', | |
expected={ | |
'u': 'same', | |
'v': {'a': 1, 'b': 2}, | |
'w': 666, | |
'x': [1, 2, 3], | |
'y': 'hola', | |
'z': [ | |
{ 'a': [11, 22, 33], 'b': 'start' }, | |
{ 'a': [44, 55], 'b': 'stop' }, | |
{ 'a': [66, 77, 88, 99], 'b': 'reset' }, | |
], | |
'deep': [{'x': [{}, {}, {'y': [[], [], [{}, {'z': 1, 'w': 2}]]}]}] | |
}, | |
received={ | |
'u': 'same', | |
'v': {'a': 1, 'c': 2}, | |
'w': '666', | |
'x': [1, 3, 2], | |
'y': 'hello', | |
'z': [ | |
{ 'a': [11, 22], 'b': 'stat' }, | |
{ 'a': [44, 55], 'd': 'stop' }, | |
{ 'a': [66, 77, 88, 99], 'b': 'reset' }, | |
], | |
'deep': [{'x': [{}, {}, {'y': [[], [], [{}, {'z': 1, 'w': -2}]]}]}] | |
}, | |
), | |
TestCase('Simple dict', | |
expected={'data': {}, 'component': 'Coordinator', 'event': 'project_loaded'}, | |
received={"component": "Coordinator", "data": {"project_name": "Microstrip"}, "event": "project_loaded"} | |
), | |
TestCase('Atoms', expected=15, received=20), | |
] | |
def bold(text): | |
return '\033[1m' + text + '\033[0m' | |
for t in test_cases: | |
diff = compare(t.expected, t.received) | |
print bold('Test:'), t.name | |
print bold('Expected:') | |
pprint(t.expected) | |
print bold('Received:') | |
pprint(t.received) | |
print bold('Differences:') | |
for d in diff: | |
print d | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment