您的位置:首页 > Web前端 > JavaScript

判断两个字符串不同的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字符串

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()之间切换
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐