Package: jycm
JYCMA flexible json diff framework for minimalist.JYCM = Json You-Cha-Ma (「is there a difference」in Chinese)Referencedeepdiffhow results are reported in JYCM is learnt from this amazing framework.how TreeLevel is designed in JYCM is learnt from this amazing framework.gojsondifFuzzy matching part of JYCM is learnt from this amazing framework.Installpip install jycmRendererYes! JYCM has arendering toolout of the box!DocumentUse JYCM with viewer directly from TerminalEasypython-mjycm--help# Usage: python -m jycm [OPTIONS]# Options:#   --interactive  Enter interactive mode#   --left TEXT    Left Json#   --right TEXT   Right Json#   --rules TEXT   Rules#   --output TEXT  The folder where the results will be dumped.#   --open         Open the browser; This is valid only if you specify the#                  output parameter.#   --help         Show this message and exit.ExampleUsemac/linuxpython-mjycm--show\--left="{\"normal-string\":\"aaaaa\",\"ignore_me-string\":\"aaaaa\",\"normal-list-1\":[{\"val\":1},{\"val\":2},{\"val\":3},{\"val\":4},{\"val\":5}],\"set_in_set\":[{\"id\":1,\"label\":\"label:1\",\"set\":[1,2,3,4,5]},{\"id\":2,\"label\":\"label:2\",\"set\":[4,5,6,7,8]}]}"\--right="{\"normal-string\":\"bbbbb\",\"ignore_me-string\":\"bbbbb\",\"normal-list-1\":[{\"val\":1},{\"val\":9},{\"val\":3},{\"val\":8},{\"what\":5}],\"set_in_set\":[{\"id\":2,\"label\":\"label:2\",\"set\":[6,5,4,7]},{\"id\":1,\"label\":\"label:1\",\"set\":[3,2,1,4,8,9]}]}"\--rules="[{\"operation\":\"ignore\",\"value\":\"^ignore_me.*\"},{\"value\":\"^set_in_set$\",\"operation\":\"operator:list:ignoreOrder\"},{\"value\":\"set_in_set->\\\\[\\\\d+\\\\]->set\",\"operation\":\"operator:list:ignoreOrder\"}]"\--left_title="Left Title"\--right_title="Right Title"or on windows:python -m jycm --show --left="{'normal-string': 'aaaaa', 'ignore_me-string': 'aaaaa', 'normal-list-1': [{'val': 1}, {'val': 2}, {'val': 3}, {'val': 4}, {'val': 5}], 'set_in_set': [{'id': 1, 'label': 'label:1', 'set': [1, 2, 3, 4, 5]}, {'id': 2, 'label': 'label:2', 'set': [4, 5, 6, 7, 8]}]}" --right="{'normal-string': 'bbbbb', 'ignore_me-string': 'bbbbb', 'normal-list-1': [{'val': 1}, {'val': 9}, {'val': 3}, {'val': 8}, {'what': 5}], 'set_in_set': [{'id': 2, 'label': 'label:2', 'set': [6, 5, 4, 7]}, {'id': 1, 'label': 'label:1', 'set': [3, 2, 1, 4, 8, 9]}]}" --rules="[{'operation': 'ignore', 'value': '^ignore_me.*'}, {'value': '^set_in_set$', 'operation': 'operator:list:ignoreOrder'}, {'value': 'set_in_set->\\[\\d+\\]->set', 'operation': 'operator:list:ignoreOrder'}]"when the json are too large you can pass file pathpython-mjycm--show--left_file/xxxx/your_left.json--right_file~/xxxx/your_right.jsonthen you will see a popup like this:InteractiveYou can play with cli using:python-mjycm--interactiveShow casesHere's some examples showing you what you can do with JYCM.
Only the results without configuration are shown below. (in case you wonder why things are not consistence here)NotesKindly suggestion from@TonisPiipWhy you wantno_pairs=Trueinycm.to_dict(no_pairs=True)?pairsare for rendering purpose (you can see the usage here atreact-jycm-viewer)usuallypairsare pretty large json (especially for somerelocationdiff)so if you just want to get the diff you don't needpairsDefault behaviourDiff two json objects are easy:Basically you do this:fromjycm.jycmimportYouchamaJsonDifferleft={}# your jsonright={}# your jsonycm=YouchamaJsonDiffer(left,right)diff_result=ycm.get_diff()"""ycm.get_diff()are the same asycm.diff()ycm.to_dict*("""Codefromjycm.helperimportmake_ignore_order_funcfromjycm.jycmimportYouchamaJsonDifferfromjycm.helperimportdump_html_output,open_urlfromjycm.jycmimportYouchamaJsonDifferleft={"a":1,"b":2,"d":"12345","f":False,"e":[{"x":1,"y":1},{"x":2,"y":2},{"x":3,"y":3},{"x":4,"y":4},]}right={"a":1,"b":3,"c":4,"f":True,"e":[{"x":0,"y":1},{"x":2,"y":2},{"x":3,"y":3},{"x":5,"y":5},]}ycm=YouchamaJsonDiffer(left,right)diff_result=ycm.get_diff()# new API# legacy usage:# ycm.diff()# diff_result = ycm.to_dict()# you can find generated html in the folderoutput_dir="/Users/xxx/jycm-example-1"# you can directly view it by clicking the index.html file inside the folderurl=dump_html_output(left,right,diff_result,output_dir)# if you want to open it from pythonopen_url(url)expected={'dict:add':[{'left':'__NON_EXIST__','left_path':'','right':4,'right_path':'c'}],'dict:remove':[{'left':'12345','left_path':'d','right':'__NON_EXIST__','right_path':''}],'list:add':[{'left':'__NON_EXIST__','left_path':'','right':{'x':5,'y':5},'right_path':'e->[3]'}],'list:remove':[{'left':{'x':4,'y':4},'left_path':'e->[3]','right':'__NON_EXIST__','right_path':''}],'value_changes':[{'left':2,'left_path':'b','new':3,'old':2,'right':3,'right_path':'b'},{'left':1,'left_path':'e->[0]->x','new':0,'old':1,'right':0,'right_path':'e->[0]->x'},{'left':False,'left_path':'f','new':True,'old':False,'right':True,'right_path':'f'}]}assertycm.to_dict(no_pairs=True)==expectedGraphIgnore OrderCodefromjycm.helperimportmake_ignore_order_funcfromjycm.jycmimportYouchamaJsonDifferleft={"ignore_order":[1,2,3],"not_ignore_order":[1,2,3]}right={"ignore_order":[3,2,1],"not_ignore_order":[3,2,1]}ycm=YouchamaJsonDiffer(left,right,ignore_order_func=make_ignore_order_func(["^ignore_order$"]))diff_result=ycm.get_diff()# new APIexpected={'list:add':[{'left':'__NON_EXIST__','left_path':'','right':2,'right_path':'not_ignore_order->[1]'},{'left':'__NON_EXIST__','left_path':'','right':1,'right_path':'not_ignore_order->[2]'}],'list:remove':[{'left':1,'left_path':'not_ignore_order->[0]','right':'__NON_EXIST__','right_path':''},{'left':2,'left_path':'not_ignore_order->[1]','right':'__NON_EXIST__','right_path':''}]}# legacy usage:# ycm.diff()# diff_result = ycm.to_dict()assertycm.diff_result(no_pairs=True)==expectedGraphDiff set-in-setCodefromjycm.helperimportmake_ignore_order_funcfromjycm.jycmimportYouchamaJsonDifferleft={"set_in_set":[{"id":1,"label":"label:1","set":[1,5,3]},{"id":2,"label":"label:2","set":[4,5,6]}]}right={"set_in_set":[{"id":2,"label":"label:2","set":[6,5,4]},{"id":1,"label":"label:1111","set":[3,2,1]}]}ycm=YouchamaJsonDiffer(left,right,ignore_order_func=make_ignore_order_func([f"^set_in_set$",f"^set_in_set->\\[\\d+\\]->set$"]))ycm.diff()expected={'list:add':[{'left':'__NON_EXIST__','right':2,'left_path':'','right_path':'set_in_set->[1]->set->[1]'}],'list:remove':[{'left':5,'right':'__NON_EXIST__','left_path':'set_in_set->[0]->set->[1]','right_path':''}],'value_changes':[{'left':'label:1','right':'label:1111','left_path':'set_in_set->[0]->label','right_path':'set_in_set->[1]->label','old':'label:1','new':'label:1111'}]}assertycm.to_dict(no_pairs=True)==expectedGraphCustom operatorDefine an operatorimportmathfromjycm.operatorimportBaseOperatorclassL2DistanceOperator(BaseOperator):__operator_name__="operator:l2distance"__event__="operator:l2distance"def__init__(self,path_regex,distance_threshold):super().__init__(path_regex=path_regex)self.distance_threshold=distance_thresholddefdiff(self,level:'TreeLevel',instance,drill:bool)->Tuple[bool,float]:print("damn")distance=math.sqrt((level.left["x"]-level.right["x"])**2+(level.left["y"]-level.right["y"])**2)info={"distance":distance,"distance_threshold":self.distance_threshold,"pass":distance<self.distance_threshold}ifnotdrill:instance.report(self.__event__,level,info)returnTrue,1ifinfo["pass"]else0And use itfromjycm.jycmimportYouchamaJsonDifferleft={"distance_ok":{"x":1,"y":1},"distance_too_far":{"x":5,"y":5},}right={"distance_ok":{"x":2,"y":2},"distance_too_far":{"x":7,"y":9},}ycm=YouchamaJsonDiffer(left,right,custom_operators=[L2DistanceOperator(f"distance.*",3),])expected={'operator:l2distance':[{'left':{'x':1,'y':1},'right':{'x':2,'y':2},'left_path':'distance_ok','right_path':'distance_ok','distance':1.4142135623730951,'distance_threshold':3,'pass':True},{'left':{'x':5,'y':5},'right':{'x':7,'y':9},'left_path':'distance_too_far','right_path':'distance_too_far','distance':4.47213595499958,'distance_threshold':3,'pass':False}]}assert{**ycm.to_dict(),"pairs":[]}==expectedPhilosophySince determining two things are equal or not is heavily depend on the context,
it is not possible to build a json diff tool to meet all requirements.JYCM choose another way: making it easy to compare values.JYCM allows users just need to focus on defining the differing logic or what a distance is between
two values and
JYCM will take care all the other dirty works such as array-item-matching,
ignoring-array-order, recursively comparing and calculating similarity.By the way, JYCM uses algorithms below to match items in array:exactly matchingfuzzy matchingwith orderLCSEdit distancewithout orderBrute forceKuhn–MunkresSpeaking of flexibility, to make a new operator, you just to need to extend a class calledBaseOperatorwith such function signature:diff: (level: 'TreeLevel', instance, drill: bool) => [float, boolean]where the first return is a float number between zero and one for describing how similarlevel.leftandlevel.rightare and the second
return is used to indicate whether comparing process should stop here.Contributerequirementspipinstall-rrequirements-dev.txtReleasesgitcheckoutmaster&&gitpull
bumpversion{patch|minor|major}gitpush&&gitpush--tagsrun test with covmaketestcovmake docsmakedocs