forked from QIICR/dcmqi
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This one is from https://github.com/ChannelIQ/jsoncompare with minor modification submitted in this PR ChannelIQ/jsoncompare#8. Without the modification, it is required that the compared JSONs have identical number of keys, which is not logical, since we are specifically interested in ignoring specified keys. Related to QIICR#97
- Loading branch information
Showing
1 changed file
with
168 additions
and
0 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
import json | ||
from pprint import pprint | ||
|
||
class Stack: | ||
def __init__(self): | ||
self.stack_items = [] | ||
|
||
def append(self, stack_item): | ||
self.stack_items.append(stack_item) | ||
return self | ||
|
||
def __repr__(self): | ||
stack_dump = '' | ||
for item in self.stack_items: | ||
stack_dump += str(item) | ||
return stack_dump | ||
|
||
def __str__(self): | ||
stack_dump = '' | ||
for item in self.stack_items: | ||
stack_dump += str(item) | ||
return stack_dump | ||
|
||
|
||
class StackItem: | ||
def __init__(self, reason, expected, actual): | ||
self.reason = reason | ||
self.expected = expected | ||
self.actual = actual | ||
|
||
def __repr__(self): | ||
return 'Reason: {0}\nExpected:\n{1}\nActual:\n{2}' \ | ||
.format(self.reason, _format_value(self.expected), _format_value(self.actual)) | ||
|
||
def __str__(self): | ||
return '\n\nReason: {0}\nExpected:\n{1}\nActual:\n{2}' \ | ||
.format(self.reason, _format_value(self.expected), _format_value(self.actual)) | ||
|
||
|
||
def _indent(s): | ||
return '\n'.join(' ' + line for line in s.splitlines()) | ||
|
||
|
||
def _format_value(value): | ||
return _indent(_generate_pprint_json(value)) | ||
|
||
|
||
def _generate_pprint_json(value): | ||
return json.dumps(value, sort_keys=True, indent=4) | ||
|
||
|
||
def _is_dict_same(expected, actual, ignore_value_of_keys): | ||
# DAN - I had to flip flop this | ||
for key in expected: | ||
if not key in actual: | ||
return False, \ | ||
Stack().append( | ||
StackItem('Expected key "{0}" Missing from Actual' | ||
.format(key), | ||
expected, | ||
actual)) | ||
|
||
if not key in ignore_value_of_keys: | ||
# have to change order | ||
#are_same_flag, stack = _are_same(actual[key], expected[key], ignore_value_of_keys) | ||
are_same_flag, stack = _are_same(expected[key], actual[key],ignore_value_of_keys) | ||
if not are_same_flag: | ||
return False, \ | ||
stack.append(StackItem('Different values', expected[key], actual[key])) | ||
return True, Stack() | ||
|
||
def _is_list_same(expected, actual, ignore_value_of_keys): | ||
for i in xrange(len(expected)): | ||
are_same_flag, stack = _are_same(expected[i], actual[i], ignore_value_of_keys) | ||
if not are_same_flag: | ||
return False, \ | ||
stack.append( | ||
StackItem('Different values (Check order)', expected[i], actual[i])) | ||
return True, Stack() | ||
|
||
def _bottom_up_sort(unsorted_json): | ||
if isinstance(unsorted_json, list): | ||
new_list = [] | ||
for i in xrange(len(unsorted_json)): | ||
new_list.append(_bottom_up_sort(unsorted_json[i])) | ||
return sorted(new_list) | ||
|
||
elif isinstance(unsorted_json, dict): | ||
new_dict = {} | ||
for key in sorted(unsorted_json): | ||
new_dict[key] = _bottom_up_sort(unsorted_json[key]) | ||
return new_dict | ||
|
||
else: | ||
return unsorted_json | ||
|
||
def _are_same(expected, actual, ignore_value_of_keys, ignore_missing_keys=False): | ||
# Check for None | ||
if expected is None: | ||
return expected == actual, Stack() | ||
|
||
# Ensure they are of same type | ||
if type(expected) != type(actual): | ||
return False, \ | ||
Stack().append( | ||
StackItem('Type Mismatch: Expected Type: {0}, Actual Type: {1}' | ||
.format(type(expected), type(actual)), | ||
expected, | ||
actual)) | ||
|
||
# Compare primitive types immediately | ||
if type(expected) in (int, str, bool, long, float, unicode): | ||
return expected == actual, Stack() | ||
|
||
# Ensure collections have the same length (if applicable) | ||
if ignore_missing_keys: | ||
# Ensure collections has minimum length (if applicable) | ||
# This is a short-circuit condition because (b contains a) | ||
if len(expected) > len(actual): | ||
return False, \ | ||
Stack().append( | ||
StackItem('Length Mismatch: Minimum Expected Length: {0}, Actual Length: {1}' | ||
.format(len(expected), len(actual)), | ||
expected, | ||
actual)) | ||
|
||
else: | ||
# Ensure collections has same length | ||
if len(expected) != len(actual): | ||
return False, \ | ||
Stack().append( | ||
StackItem('Length Mismatch: Expected Length: {0}, Actual Length: {1}' | ||
.format(len(expected), len(actual)), | ||
expected, | ||
actual)) | ||
|
||
|
||
|
||
if isinstance(expected, dict): | ||
return _is_dict_same(expected, actual, ignore_value_of_keys) | ||
|
||
if isinstance(expected, list): | ||
return _is_list_same(expected, actual, ignore_value_of_keys) | ||
|
||
return False, Stack().append(StackItem('Unhandled Type: {0}'.format(type(expected)), expected, actual)) | ||
|
||
def are_same(original_a, original_b, ignore_list_order_recursively=False, ignore_missing_keys=False, ignore_value_of_keys=[]): | ||
if ignore_list_order_recursively: | ||
a = _bottom_up_sort(original_a) | ||
b = _bottom_up_sort(original_b) | ||
else: | ||
a = original_a | ||
b = original_b | ||
return _are_same(a, b, ignore_value_of_keys, ignore_missing_keys) | ||
|
||
|
||
def contains(expected_original, actual_original, ignore_list_order_recursively=False, ignore_value_of_keys=[]): | ||
if ignore_list_order_recursively: | ||
actual = _bottom_up_sort(actual_original) | ||
expected = _bottom_up_sort(expected_original) | ||
else: | ||
actual = actual_original | ||
expected = expected_original | ||
return _are_same(expected, actual, ignore_value_of_keys, True) | ||
|
||
def json_are_same(a, b, ignore_list_order_recursively=False, ignore_value_of_keys=[]): | ||
return are_same(json.loads(a), json.loads(b), ignore_list_order_recursively, ignore_value_of_keys) | ||
|