判断两个字符串不同的json是否等价(附:将等价但是不同json调整成同一字符串的方法)
2017-11-18 16:42
417 查看
在做软件和网络测试的时候,经常需要对排版格式与内部结构顺序不固定的json对象进行分析对比,而json基本语法下,同样的一个json对象,字符串变化可以千变万化,json内各个层字段顺序调转,排版变化,打乱顺序的json数组+多层嵌套,等等各种因素,都会造成对比上的困难。
以下由浅及深谈谈几种方法:(一共五种方法,四和五是无缺陷方法,将json对象转换成多叉树,再对多叉树的各个父节点的子节点左右顺序进行hashCode大小排序,这样不管json顺序或者排版如何变化,最终都将生成唯一的json字符串)
一.字符串对比,外加正则方式对比,因为json字符串可变化方式太多,出错率过高略过不谈。
二.使用json库将json字符串转换成对象,这时候可以完全排除掉空格和换行等等纯排版原因造成的差异.
如需更进一步对比,需要转换为json对象后进行递归取出所有键值对,然后通过遍历map对比,缺陷是当存在同名字段或者存在json数组的时候会出现冲突的问题,这个方法适合json层数不多或者不存在同名字段的情况.
三. 使用net.sf.json包,由于net.sf.json包在构造json对象的时候,会在内部进行有序化调整,所以等价的json对象生成的hashCode是完全一致的,可以直接使用
JSONObject.hashCode()对比,而其它包如:com.google.gson包与 vertx.core.json 包无法使用该方法,顺序稍加变化就会改变hashCode.
这是库与库之间构造json对象的数据结构差异.
以下设置了两层嵌套的内部顺序不同但是等价的json字符串
结果输出:A与B是否等价:true
net.sf.json包的缺陷在于,Integer和String类型字段生成的hashCode会相同
如:{"id": "123"} 与 {"id": 123},用net.sf.json包生成的json对象的hashCode一样,这时候会造成对比错误.
四.对(三)方法进行完善,使用net.sf.json包hashCode等价的特性,对json构造的多叉树自上而下调整子节点顺序
由于hashCode等价的特性,此时孙子节点的顺序变化完全不会影响子节点的顺序,所以一次遍历就可以完成整个多叉树树排序.
重排序方法代码如下:
用对照字符串的方法,可完全避免Integer和String无法区分的情况
五.对于其它json库,如com.google.gson包与 vertx.core.json 包,构造多叉树后,自下而上调整子节点顺序,步骤如下
1.找出所有最小树,也就是子节点不是jsonObject或者jsonArray,而是基本键值对的情况
2.对所有最小树进行排序
3.往上递归,父节点树+排序完成的树进行新的排序,直到最顶层,完成整个多叉树树排序
以com.google.gson包为例:
多叉树节点对象:
构造json对象并判断:
注:由于net.sf.json包存在hashCode等价特性,而其它包不存在(需要toString),所以在排序时需要灵活变化排序规则,在toString().hashCode()和.hashCode()之间切换
以下由浅及深谈谈几种方法:(一共五种方法,四和五是无缺陷方法,将json对象转换成多叉树,再对多叉树的各个父节点的子节点左右顺序进行hashCode大小排序,这样不管json顺序或者排版如何变化,最终都将生成唯一的json字符串)
一.字符串对比,外加正则方式对比,因为json字符串可变化方式太多,出错率过高略过不谈。
二.使用json库将json字符串转换成对象,这时候可以完全排除掉空格和换行等等纯排版原因造成的差异.
如需更进一步对比,需要转换为json对象后进行递归取出所有键值对,然后通过遍历map对比,缺陷是当存在同名字段或者存在json数组的时候会出现冲突的问题,这个方法适合json层数不多或者不存在同名字段的情况.
三. 使用net.sf.json包,由于net.sf.json包在构造json对象的时候,会在内部进行有序化调整,所以等价的json对象生成的hashCode是完全一致的,可以直接使用
JSONObject.hashCode()对比,而其它包如:com.google.gson包与 vertx.core.json 包无法使用该方法,顺序稍加变化就会改变hashCode.
这是库与库之间构造json对象的数据结构差异.
以下设置了两层嵌套的内部顺序不同但是等价的json字符串
String a= "{\n" + " \"obj_1\": \"name\",\n" + " \"field_1\": \"11\",\n" + " \"list_1\": [\n" + " {\n" + " \"obj_2.1\": \"obj_2.1\",\n" + " \"field_2.1\": \"21\"\n" + " },\n" + " {\n" + " \"obj_2.2\": \"obj_2.2\",\n" + " \"field_2.2\": \"22\"\n" + " },\n" + "\t\t[ \n" + "\t\t{\n" + " \"obj_2.3.1\": \"obj_2.3.1\",\n" + " \"field_2.3.1\": \"231\"\n" + " },\n" + " {\n" + " \"obj_2.3.2\": \"obj_2.3.2\",\n" + " \"field_2.3.2\": \"232\"\n" + " }\n" + "\t\t\n" + "\t\t]\n" + " ]\n" + "}"; String b="{\n" + " \"field_1\": \"11\",\n" + " \"obj_1\": \"name\",\n" + " \"list_1\": [\n" + " \n" + " {\n" + "\t\t \"field_2.2\": \"22\",\n" + " \"obj_2.2\": \"obj_2.2\"\n" + " },\n" + "\t\t\n" + "\t\t[ \n" + "\t\t{\n" + " \"obj_2.3.1\": \"obj_2.3.1\",\n" + " \"field_2.3.1\": \"231\"\n" + " },\n" + " {\n" + " \"obj_2.3.2\": \"obj_2.3.2\",\n" + " \"field_2.3.2\": \"232\"\n" + " }\n" + "\t\t],\n" + "\t\t\n" + "\t\t{\n" + " \"field_2.1\": \"21\",\n" + "\t\t\t\"obj_2.1\": \"obj_2.1\"\n" + " \n" + " }\n" + " ]\n" + "}"; JSONObject jsA= JSONObject.fromObject(a); JSONObject jsB= JSONObject.fromObject(b); System.out.println("A与B是否等价:"+(jsA.hashCode()==jsB.hashCode()));
结果输出:A与B是否等价:true
net.sf.json包的缺陷在于,Integer和String类型字段生成的hashCode会相同
如:{"id": "123"} 与 {"id": 123},用net.sf.json包生成的json对象的hashCode一样,这时候会造成对比错误.
String a="{\"id\": \"123\"}"; String b="{\"id\": 123}"; Long time=System.currentTimeMillis(); JSONObject jsA= JSONObject.fromObject(a); JSONObject jsB= JSONObject.fromObject(b); System.out.println("A与B是否等价:"+(jsA.hashCode()==jsB.hashCode()));结果输出:A与B是否等价:true
四.对(三)方法进行完善,使用net.sf.json包hashCode等价的特性,对json构造的多叉树自上而下调整子节点顺序
由于hashCode等价的特性,此时孙子节点的顺序变化完全不会影响子节点的顺序,所以一次遍历就可以完成整个多叉树树排序.
重排序方法代码如下:
//自上而下将json对象的各个元素重新排序,采用将json对象排序后重新组装的方式 static void arrangeJson(Object js){ if(js instanceof JSONObject){ JSONObject jsCopy = JSONObject.fromObject(js.toString()); //将json对象复制一份,进行递归遍历取值 Iterator i=jsCopy.entrySet().iterator(); ArrayList<Object> arr=new ArrayList<Object>(); while(i.hasNext()){ Map.Entry entry = (Map.Entry)i.next(); arr.add(entry.getKey().toString()); //逐个取出子节点对象 //System.out.println(entry.getKey() + " " + entry.getValue()+" "+jsCopy.get(entry.getKey()).getClass()); ((JSONObject)js).remove(entry.getKey().toString()); //清空旧的子元素 } sortArr(arr); for(int n=0;n<arr.size();n++){ //System.out.println("arr="+arr.get(n)); String key=arr.get(n).toString(); if(jsCopy.get(key) instanceof JSONObject||(jsCopy.get(key) instanceof JSONArray)){ arrangeJson(jsCopy.get(key)); //递归调整json对象 } ((JSONObject)js).put(key,jsCopy.get(key)); //重新组装序列化的子元素 } } if(js instanceof JSONArray) { JSONArray jsCopy=JSONArray.fromObject(js.toString()); ArrayList<Object> arr=new ArrayList<Object>(); for(int n=0;n<jsCopy.size();n++){ arr.add(jsCopy.get(n)); ((JSONArray) js).remove(0); } sortArr(arr); for(int n=0;n<arr.size();n++){ //System.out.println("arr_"+n+arr.get(n)); arrangeJson((Object)arr.get(n)); ((JSONArray) js).add((Object)arr.get(n)); } } } //将数组元素按照哈希码从小到大重新排序 private static void sortArr(ArrayList<Object> arr){ int len=arr.size(); int[] n=new int[len]; ArrayList<Object> arrCopy=(ArrayList<Object>)arr.clone(); Object[] obj=new Object[len]; for(int i=0;i<len;i++){ n[i]=arrCopy.get(i).hashCode(); obj[i]=arrCopy.get(i); arr.remove(0); } for(int i=0;i<len;i++){ for(int y=i+1;y<len;y++){ if(n[i]<n[y]){ int x=n[y]; n[y]=n[i]; n[i]=x; Object s=obj[y]; obj[y]=obj[i]; obj[i]=s; } } } for(int i=0;i<len;i++){ arr.add(obj[i]); } }构造json对象并对比:
public static void main(String[] args) { String a= "{\n" + " \"obj_1\": \"name\",\n" + " \"field_1\": \"1 bd68 1\",\n" + " \"list_1\": [\n" + " {\n" + " \"obj_2.1\": \"obj_2.1\",\n" + " \"field_2.1\": \"21\"\n" + " },\n" + " {\n" + " \"obj_2.2\": \"obj_2.2\",\n" + " \"field_2.2\": \"22\"\n" + " },\n" + "\t\t[ \n" + "\t\t{\n" + " \"obj_2.3.1\": \"obj_2.3.1\",\n" + " \"field_2.3.1\": \"231\"\n" + " },\n" + " {\n" + " \"obj_2.3.2\": \"obj_2.3.2\",\n" + " \"field_2.3.2\": \"232\"\n" + " }\n" + "\t\t\n" + "\t\t]\n" + " ]\n" + "}"; String b="{\n" + " \"field_1\": \"11\",\n" + " \"obj_1\": \"name\",\n" + " \"list_1\": [\n" + " \n" + " {\n" + "\t\t \"field_2.2\": \"22\",\n" + " \"obj_2.2\": \"obj_2.2\"\n" + " },\n" + "\t\t\n" + "\t\t[ \n" + "\t\t{\n" + " \"obj_2.3.1\": \"obj_2.3.1\",\n" + " \"field_2.3.1\": \"231\"\n" + " },\n" + " {\n" + " \"obj_2.3.2\": \"obj_2.3.2\",\n" + " \"field_2.3.2\": \"232\"\n" + " }\n" + "\t\t],\n" + "\t\t\n" + "\t\t{\n" + " \"field_2.1\": \"21\",\n" + "\t\t\t\"obj_2.1\": \"obj_2.1\"\n" + " \n" + " }\n" + " ]\n" + "}"; arrangeJson(jsA); arrangeJson(jsB); System.out.println("A与B是否等价:"+(jsA.toString().equals(jsB.toString())));//此处应对照字符串 }结果输出:A与B是否等价:true
用对照字符串的方法,可完全避免Integer和String无法区分的情况
五.对于其它json库,如com.google.gson包与 vertx.core.json 包,构造多叉树后,自下而上调整子节点顺序,步骤如下
1.找出所有最小树,也就是子节点不是jsonObject或者jsonArray,而是基本键值对的情况
2.对所有最小树进行排序
3.往上递归,父节点树+排序完成的树进行新的排序,直到最顶层,完成整个多叉树树排序
以com.google.gson包为例:
多叉树节点对象:
public static class jsonObj{ //多叉树双向链表对象 int fatherId=-1; //父节点的ID ArrayList<Integer> sonId=new ArrayList<Integer>(); //子节点ID数组 ArrayList<Object> sonKey=new ArrayList<Object>(); //子节点键值,当前存放的对象为JsonArray时为空 Object obj; //当前存放的对象 boolean isSort; //当前对象是否已经完成排序 }重排序方法:
static Object getArrangeJson(Object js){ ArrayList<jsonObj> jsonObjs = new ArrayList<jsonObj>(); //声明链表对象 setObjArr(js,jsonObjs); //将json对象自上而下转换成链表对象 arrangeArr(jsonObjs); //将链表对象自下而上调整顺序 return jsonObjs.get(0).obj; } static void arrangeArr(ArrayList<jsonObj> jsonObjs) { int len = jsonObjs.size(); boolean[] isSort = new boolean[len]; boolean isAllSort = false; while (!isAllSort) { //判断是否全部完成排序 for (int i = 0; i < len; i++) { isAllSort = isSort[i]; if (!isAllSort) break; } for (int n = 0; n < len; n++) { int fatherId = jsonObjs.get(n).fatherId; if (fatherId != -1 && is_End_or_Sort(jsonObjs, n, fatherId)) { jsonObj fatherObj = jsonObjs.get(fatherId); ArrayList<Integer> sonId = fatherObj.sonId; if (fatherObj.obj instanceof JsonObject) { sortArr(fatherObj.sonKey, fatherObj.sonId); JsonObject obj = new JsonObject(); for (int m = 0; m < fatherObj.sonId.size(); m++) { obj.add(fatherObj.sonKey.get(m).toString(), (JsonElement) jsonObjs.get(fatherObj.sonId.get(m)).obj); jsonObjs.get(fatherObj.sonId.get(m)).isSort = true; isSort[fatherObj.sonId.get(m)] = true; } fatherObj.obj = obj; fatherObj.isSort = true; isSort[fatherId] = true; } if (fatherObj.obj instanceof JsonArray) { ArrayList<Object> list = new ArrayList<Object>(); for (int m = 0; m < ((JsonArray) fatherObj.obj).size(); m++) { list.add(((JsonArray) fatherObj.obj).get(m)); } sortArr(list, fatherObj.sonId); JsonArray obj = new JsonArray(); for (int m = 0; m < fatherObj.sonId.size(); m++) { obj.add((JsonElement) jsonObjs.get(fatherObj.sonId.get(m)).obj); jsonObjs.get(fatherObj.sonId.get(m)).isSort = true; isSort[fatherObj.sonId.get(m)] = true; } fatherObj.obj = obj; fatherObj.isSort = true; isSort[fatherId] = true; } } } } } static boolean is_End_or_Sort(ArrayList<jsonObj> jsonObjs,int id,int fatherId){ boolean b=true; ArrayList<Integer> sonId= jsonObjs.get(fatherId).sonId; for(int n=0;n<sonId.size();n++){ if(!(jsonObjs.get(sonId.get(n)).obj instanceof com.google.gson.JsonPrimitive) && !jsonObjs.get(sonId.get(n)).isSort){ b=false; break; } } return b; } static void contrast(Object a,Object b){ //将jsonObject或JsonArray的内部元素按照规则重新排序 boolean i = false; System.out.println("json对象a序列化="+a.toString()); System.out.println("json对象b序列化="+b.toString()); // if(a.hashCode()==b.hashCode()){ //hashCode与toString两种比较方法均可 // i=true; // } if(a.toString().equals(b.toString())){ i=true; } System.out.println("两个json对象是否等价:"+i); } //将json对象自上而下逐个分解转换成链表对象 static void setObjArr(Object js,ArrayList<jsonObj> jsonObjs){ jsonObj obj; if(jsonObjs.size()==0) { obj = new jsonObj(); obj.obj=js; jsonObjs.add(obj); } else{ obj=jsonObjs.get(jsonObjs.size()-1); } int id=jsonObjs.size()-1; if(js instanceof JsonObject) { Iterator i$ = ((JsonObject) js).entrySet().iterator(); //递归遍历子元素 //ArrayList<> while (i$.hasNext()) { Map.Entry entry = (Map.Entry) i$.next(); jsonObj sonObj = new jsonObj(); sonObj.fatherId=id; sonObj.obj=entry.getValue(); obj.sonId.add(jsonObjs.size()); jsonObjs.add(sonObj); obj.sonKey.add(entry.getKey().toString()); if (!(entry.getValue() instanceof com.google.gson.JsonPrimitive)) { setObjArr(entry.getValue(),jsonObjs); //自上而下递归 } } } if(js instanceof JsonArray){ for(int n=0;n<((JsonArray) js).size();n++){ jsonObj sonObj = new jsonObj(); sonObj.fatherId=id; sonObj.obj=((JsonArray) js).get(n); obj.sonId.add(jsonObjs.size()); jsonObjs.add(sonObj); setObjArr(((JsonArray) js).get(n),jsonObjs); //自上而下递归 } } } //将数组元素按照哈希码从小到大重新排序 private static boolean sortArr(ArrayList<Object> arr, ArrayList<Integer> idArr){ boolean b=false; int len=arr.size(); int[] n=new int[len]; for(int i=0;i<len;i++){ n[i]=arr.get(i).toString().hashCode(); } for(int i=0;i<len;i++){ for(int y=i+1;y<len;y++){ if(n[i]<n[y]){ int x=n[y]; n[y]=n[i]; n[i]=x; Object s=arr.get(y); arr.set(y,arr.get(i)); arr.set(i,s); x = idArr.get(y); idArr.set(y,idArr.get(i)); idArr.set(i,x); b=true; } } } return b; }
构造json对象并判断:
String a= "{\n" + " \"obj_1\": \"name\",\n" + " \"field_1\": \"11\",\n" + " \"list_1\": [\n" + " {\n" + " \"obj_2.1\": \"obj_2.1\",\n" + " \"field_2.1\": \"21\"\n" + " },\n" + " {\n" + " \"obj_2.2\": \"obj_2.2\",\n" + " \"field_2.2\": \"22\"\n" + " },\n" + "\t\t[ \n" + "\t\t{\n" + " \"obj_2.3.1\": \"obj_2.3.1\",\n" + " \"field_2.3.1\": \"231\"\n" + " },\n" + " {\n" + " \"obj_2.3.2\": \"obj_2.3.2\",\n" + " \"field_2.3.2\": \"232\"\n" + " }\n" + "\t\t\n" + "\t\t]\n" + " ]\n" + "}"; String b="{\n" + " \"field_1\": \"11\",\n" + " \"obj_1\": \"name\",\n" + " \"list_1\": [\n" + " \n" + " {\n" + "\t\t \"field_2.2\": \"22\",\n" + " \"obj_2.2\": \"obj_2.2\"\n" + " },\n" + "\t\t\n" + "\t\t[ \n" + "\t\t{\n" + " \"obj_2.3.1\": \"obj_2.3.1\",\n" + " \"field_2.3.1\": \"231\"\n" + " },\n" + " {\n" + " \"obj_2.3.2\": \"obj_2.3.2\",\n" + " \"field_2.3.2\": \"232\"\n" + " }\n" + "\t\t],\n" + "\t\t\n" + "\t\t{\n" + " \"field_2.1\": \"21\",\n" + "\t\t\t\"obj_2.1\": \"obj_2.1\"\n" + " \n" + " }\n" + " ]\n" + "}"; JsonObject jsA = paser.parse(b).getAsJsonObject(); JsonObject jsB = paser.parse(b).getAsJsonObject(); //以下两种判断均可 System.out.println("A与B是否等价:"+(getArrangeJson(jsA).toString().equals(getArrangeJson(jsB).toString()))); System.out.println("A与B是否等价:"+(getArrangeJson(jsA).hashCode()==getArrangeJson(jsB).hashCode())); 结果输出: A与B是否等价:true A与B是否等价:true
注:由于net.sf.json包存在hashCode等价特性,而其它包不存在(需要toString),所以在排序时需要灵活变化排序规则,在toString().hashCode()和.hashCode()之间切换
相关文章推荐
- 编写一个函数,判断两个字符串是否是是相互打乱的,也就是说它们有着相同的字符,但是对应不同的顺序。
- 当获取相似数据时,使用不同方法调用不同sp,但是使用同一个方法去用IIDataReader或者SqlDataReader读取数据时需要判断column name是否存在。
- go判断两个字符串是否是是相互打乱的,也就是说它们有着相同的字符,但 是对应不同的顺序。
- 在输入的字符串中判断是否有a,多种方法(比较前两种不同的表示方法)
- JavaScript中判断两个字符串是否相等的方法
- python判断字符串是否是json格式方法分享
- 设计两个不同的方法,判断一个数是否为2的阶次数
- Gson: 比较两个json是否等价(比较java bean是否相等的通用方法)
- php简单判断两个字符串是否相等的方法
- php简单判断两个字符串是否相等的方法
- 【最简单的方法】js判断字符串是否为JSON格式(20180115更新)
- python 判断字符串时是否是json格式方法
- 判断两个字符串中出现的字符是否完全一样(顺序可以不同)
- java 判断两个字符串是否由相同的字符组成 排序算法 空间换时间的方法
- JavaScript中判断两个字符串是否相等的方法
- writeObject可以写n个,但是readObject()却只能读一次,你做一下测试。 如果你需要序列化好几个类的话,建议你用json;或者自己写两个方法,一个是将对象转换为字符串,一个是将字符串
- JavaScript中判断两个字符串是否相等的方法
- C语言判断两个lpcwstr字符串是否相等的方法
- 设计两个不同的方法,判断一个数是否为2的阶次数
- C#判断两个字符串是否相等的方法