Skip to content

Instantly share code, notes, and snippets.

@rbonvall
Last active August 29, 2015 13:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rbonvall/9835300 to your computer and use it in GitHub Desktop.
Save rbonvall/9835300 to your computer and use it in GitHub Desktop.
Compare two JSONable Python data structures and enumerate their differences
#!/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
print bold('Received:')
pprint(t.received)
print
print bold('Differences:')
for d in diff:
print d
print
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment