wynnbuilder-idk/py_script/json_diff.py

161 lines
5.5 KiB
Python
Raw Normal View History

2022-06-15 18:55:04 +00:00
"""
Json diff checker for manual testing - mainly debug
"""
2021-10-18 02:05:56 +00:00
import argparse
import json
2021-10-18 02:05:56 +00:00
import sys
from recordclass import recordclass
JSONDiffReporter = recordclass('JSONDiffReporter', 'val_diff len_diff type_diff path_diff get_key')
2021-10-18 02:05:56 +00:00
def shorten(val):
"""Utility for printing large functions, auto shorten"""
if len(val) > 100:
return val[:100] + "..."
return val
2021-10-18 02:05:56 +00:00
def is_basic(val):
"""Check if the given value is a "primitive" type in json (not object)."""
return val is int or val is str or val is float or val is bool or val is list
2021-10-18 02:05:56 +00:00
def __custom_input(_):
"""Read from stdin unless user has cancelled"""
if __custom_input.alive:
try:
return input()
2021-10-18 02:05:56 +00:00
except KeyboardInterrupt:
__custom_input.alive = False
return ""
2021-10-18 02:05:56 +00:00
__custom_input.alive = True
def __print_val_diff(val1, val2, path):
print(f"{path}: Value difference")
print(f" Left: {shorten(str(val1))}")
print(f" Right: {shorten(str(val2))}")
def __print_len_diff(val1, val2, path):
print(f"{path}: Length difference")
print(f" Left (length {len(val1)}): {shorten(str(val1))}")
print(f" Right (length {len(val2)}): {shorten(str(val2))}")
def __print_type_diff(type1, type2, path):
print(f"{path}: Type difference [{str(type1)} != {str(type2)}]")
#def __print_path_diff(left, right, key, path, side):
def __print_path_diff(_1, _2, key, path, side):
if side:
print(f"{path}.{key}: Contained in right but not left")
2022-07-20 04:27:06 +00:00
print(f" Value: {shorten(str(_2[key]))}")
2021-10-18 02:05:56 +00:00
else:
print(f"{path}.{key}: Contained in left but not right")
2022-07-20 04:27:06 +00:00
print(f" Value: {shorten(str(_1[key]))}")
2021-10-18 02:05:56 +00:00
# Default diff reporter (just prints everything)
JSON_DIFF_PRINTER = JSONDiffReporter(
__print_val_diff,
__print_len_diff,
__print_type_diff,
__print_path_diff,
__custom_input
)
def __val_diff(val1, val2, path):
errmsg = (f"{path}: Value difference\n"
+ f" Left: {shorten(str(val1))}\n"
+ f" Right: {shorten(str(val2))}")
raise ValueError(errmsg)
def __len_diff(val1, val2, path):
errmsg = (f"{path}: Length difference\n"
+ f" Left (length {len(val1)}): {shorten(str(val1))}\n"
+ f" Right (length {len(val2)}): {shorten(str(val2))}")
raise ValueError(errmsg)
def __type_diff(type1, type2, path):
raise TypeError(f"{path}: Type difference [{str(type1)} != {str(type2)}]")
#def __print_path_diff(left, right, key, path, side):
def __path_diff(_1, _2, key, path, side):
if side:
errmsg = f"{path}.{key}: Contained in right but not left\n"
2022-07-20 04:27:06 +00:00
errmsg += f" Value: {shorten(str(_2[key]))}"
2021-10-18 02:05:56 +00:00
else:
errmsg = f"{path}.{key}: Contained in left but not right\n"
2022-07-20 04:27:06 +00:00
errmsg += f" Value: {shorten(str(_1[key]))}"
2021-10-18 02:05:56 +00:00
raise AttributeError(errmsg)
def get_test_diff_handler(get_key):
"""Make a JSON diff handler that throws errors on failure.
:param: get_key: key getter func
"""
return JSONDiffReporter(__val_diff, __len_diff, __type_diff, __path_diff, get_key)
def list_diff(reporter, list1, list2, path) -> bool:
"""Compute list difference between two object lists (compare by key)"""
2021-07-11 16:22:34 +00:00
print(f"Encountered object list {path}, enter match key: ", end="", file=sys.stderr)
2021-10-18 02:05:56 +00:00
key = reporter.get_key(path)
if key == "":
2021-07-11 16:22:34 +00:00
if list1 != list2:
2021-10-18 02:05:56 +00:00
reporter.val_diff(list1, list2, path)
2021-07-11 16:22:34 +00:00
else:
left = {x[key]: x for x in list1}
right = {x[key]: x for x in list2}
2021-10-18 02:05:56 +00:00
object_diff(reporter, left, right, path)
2021-07-11 16:22:34 +00:00
2021-10-18 02:05:56 +00:00
def object_diff(reporter, obj1, obj2, path) -> bool:
"""Compute object difference between two objects... kinda"""
for (k, val) in obj1.items():
if k in obj2:
obj = obj2[k]
2021-10-18 02:05:56 +00:00
type1 = type(val)
type2 = type(obj)
if type1 != type2:
2021-10-18 02:05:56 +00:00
reporter.type_diff(type1, type2, f"{path}.{k}")
elif type1 is list:
if len(val) != len(obj):
reporter.len_diff(val, obj, f"{path}.{k}")
elif len(val) == 0:
continue
elif is_basic(type(val[0])):
if val != obj:
reporter.val_diff(val, obj, f"{path}.{k}")
continue
list_diff(reporter, val, obj, path+"."+k)
elif is_basic(type1) or val is None or obj2 is None:
if val != obj:
reporter.val_diff(val, obj, f"{path}.{k}")
else:
2021-10-18 02:05:56 +00:00
object_diff(reporter, val, obj, f"{path}.{k}")
continue
2022-07-20 04:27:06 +00:00
reporter.path_diff(obj1, obj2, k, path, False)
2021-10-18 02:05:56 +00:00
for k in obj2:
if k not in obj1:
2021-10-18 02:05:56 +00:00
reporter.path_diff(obj1, obj2, k, path, True)
def json_diff(json1, json2, reporter=JSON_DIFF_PRINTER) -> bool:
"""Run the json diff tool on two json objects."""
if isinstance(json1, list) and isinstance(json2, list):
return list_diff(reporter, json1, json2, "$")
return object_diff(reporter, json1, json2, "$")
if __name__ == "__main__":
argparser = argparse.ArgumentParser(description="JSON diff utility")
argparser.add_argument('file1', action='store', type=str,
help="First file to compare"
)
argparser.add_argument('file2', action='store', type=str,
help="Second file to compare"
)
2021-10-18 02:05:56 +00:00
args = argparser.parse_args()
with open(args.file1, 'r', encoding="utf-8") as file1:
json1 = json.load(file1)
with open(args.file2, 'r', encoding="utf-8") as file2:
json2 = json.load(file2)
json_diff(json1, json2)