如何通过递归方法实现用json-diff渲染json字符串对比结果

前言

上一篇,对比了js-diff和json-diff,发现js-diff对比的结果返回的是拆分后字符串并且字符串中带有换行符跟空格,将拆分后的字符串拼接可以得到完整的两个json格式化后的信息。但是json-diff只返回了两个json中有修改的的部分,并且返回类型还要根据值类型来进行判断。

分析json-diff的结构

用两个数组嵌套对象,对象嵌套数组的比较复杂的json进行对比,获得的数据如下:

var json1 = {
 name: '小明',
 age: '22',
 hobby: ['篮球', '足球', '羽毛球'],
 grade: {
 English: ['130', '129', '135'],
 Chinese: ['120', '118', '122'],
 sports: ['90', '90', '90'],
 },
 honor: [{ desc: '获得数学竞赛第一名', info: [{ score: '100', ranking: 1 }, 2, 3] }],
}
var json2 = {
 name: '小红',
 age: '22',
 hobby: ['乒乓球', '羽毛球'],
 grade: {
 English: ['120', '129', '125'],
 Chinese: ['140', '139', '136'],
 sports: ['90', '90', '90'],
 },
 honor: [{ desc: '获得作文比赛第一名', info: [{ score: '99', rank: 2 }, 2, 3] }],
}
var jsonDiff = {
 "name": {
 "__old": "小明",
 "__new": "小红"
 },
 "hobby": [
 [
 "-",
 "篮球"
 ],
 [
 "-",
 "足球"
 ],
 [
 "+",
 "乒乓球"
 ],
 [
 " "
 ]
 ],
 "grade": {
 "English": [
 [
 "-",
 "130"
 ],
 [
 "+",
 "120"
 ],
 [
 " "
 ],
 [
 "-",
 "135"
 ],
 [
 "+",
 "125"
 ]
 ],
 "Chinese": [
 [
 "-",
 "120"
 ],
 [
 "-",
 "118"
 ],
 [
 "-",
 "122"
 ],
 [
 "+",
 "140"
 ],
 [
 "+",
 "139"
 ],
 [
 "+",
 "136"
 ]
 ]
 },
 "honor": [
 [
 "~",
 {
 "desc": {
 "__old": "获得数学竞赛第一名",
 "__new": "获得作文比赛第一名"
 },
 "info": [
 [
 "~",
 {
 "ranking__deleted": 1,
 "rank__added": 2,
 "score": {
 "__old": "100",
 "__new": "99"
 }
 }
 ],
 [
 " "
 ],
 [
 " "
 ]
 ]
 }
 ]
 ]
};

可以观察到:

  • 字符串返回{"__old":string,"__new":string},数组返回[string[,string][,object]],非数组对象返回{ "key__added": string|object, "key__deleted": string|object, "key": string|object }。
  • 字符串通过__old、__new获取
  • 非数组对象新增属性从key__added获取,删除属性从key__deleted获取
  • 数组第一个值有四种类型空格表示未修改、+表示新增、-表示删除、~表示修改

解析结构图如下:

太绕了,画图之后才看的明白一点ORZ...

用递归方法拼接json字符串

首先创建一个函数用来判断json是否为数组,renderobj(diffObj, originObj, n),diffObj表示当层的diff对象,originObj表示当层的json1对象,n表示深度,第一层深度为0,主要为了方便计算缩进。

renderobj函数:

// 定义remove和add队列
const removeList = [];
const addlist = [];
if (diffObj instanceof Array) {
 // 数组的判断
} else if (typeof diffObj == 'object'){
 if (diffObj.__new) {
 // 字符串修改
 } else {
 // 非数组对象修改
 }
}

字符串修改

当diffObj.__new为真时,__old同时有值,表示字符串做修改,此时可以直接把__new放到addList,__old存入removeList。

addlist.push(`<span style="color:red;">"${diffObj.__new}"</span>,<br/>`);
removeList.push(`<span style="color:red;">"${diffObj.__old}"</span>,<br/>`);

非数组对象修改

const { addHtml, removeHtml } = renderObject(diffObj, originObj, n);
addlist.push(addHtml);
removeList.push(removeHtml);

非数组对象要判断key的值,创建函数renderObject(diffObj, originObj, n),传参同renderobj。

const addlist = [];
const removeList = [];
addlist.push(`{</br>`);
removeList.push(`{</br>`);

// 遍历originObj,遍历diffObj...

addlist.push(`${spaceStr.repeat(n)}},</br>`);
removeList.push(`${spaceStr.repeat(n)}},</br>`);
return {
 addHtml: addlist.join(''),
 removeHtml: removeList.join(''),
};

为啥要分别遍历originObj和diffObj?因为json1对象中没有新增的key,diffObj中没有返回未修改key。

const spaceStr = ' ';
Object.keys(originObj).forEach(key => {
 const keyVal = diffObj[key];
 if (keyVal) {
 // renderobj重新判断修改类型
 const { addHtml, removeHtml } = renderobj(keyVal, originObj[key], n + 1);
 if (keyVal.__new) {
 // 当字符串修改时,将整行标红
 addlist.push('<div class="error-line-add">');
 removeList.push('<div class="error-line-remove">');
 }
 addlist.push(`${spaceStr.repeat(n + 1)}"${key}": ${addHtml}`);
 removeList.push(`${spaceStr.repeat(n + 1)}"${key}": ${removeHtml}`);
 if (keyVal.__new) {
 addlist.push(`</div>`);
 removeList.push(`</div>`);
 }
 } else {
 const remove = diffObj[key + '__deleted'];
 if (remove) {
 // 删除的属性
 removeList.push('<div class="error-line-remove">');
 removeList.push(
 `<span style="color:red;">${spaceStr.repeat(n + 1)}"${key}": ${renderJson(
 remove,
 n + 1
 )}</span></br>`
 );
 removeList.push('</div>');
 } else {
 // 没修改的属性直接存入
 addlist.push(
 `${spaceStr.repeat(n + 1)}"${key}": ${renderJson(originObj[key], n + 1)}</br>`
 );
 removeList.push(
 `${spaceStr.repeat(n + 1)}"${key}": ${renderJson(originObj[key], n + 1)}</br>`
 );
 }
 }
})
Object.keys(diffObj).forEach(key => {
 // 新增的属性,json1中没有,从diffObj中遍历
 if (key.includes('__added')) {
 addlist.push('<div class="error-line-add">');
 addlist.push(
 `<span style="color: red;">${spaceStr.repeat(n + 1)}"${key.replace('__added', '')}": "${diffObj[key]}",</br></span>`
 );
 addlist.push('</div>');
 }
});

数组对象修改

遍历diffObj,判断item[0]的值,当值为空格时,需要从json1中获取原属性值,diffObj中空格所在的索引位置有可能和originObj中索引的位置不同,要排除掉'+'新增的成员;当值为'+'时,直接将值存入addList;当值为'-'时,直接将值存入removeList;当值为'~'时,表示有修改,修改类型不确定需要重新循环判断。

addlist.push(`[</br>`);
removeList.push(`[</br>`);
diffObj.forEach((item, i) => {
 switch (item[0]) {
 case '~': {
 // 有修改,重新判断修改的值的类型
 const { addHtml, removeHtml } = renderobj(item[1], originObj[i], n + 1);
 addlist.push(`${spaceStr.repeat(n + 1)}${addHtml}`);
 removeList.push(`${spaceStr.repeat(n + 1)}${removeHtml}`);
 break;
 }
 case '-': {
 // 删除属性
 removeList.push('<div class="error-line-remove">');
 removeList.push(
 `<span style="color: red;">${spaceStr.repeat(n + 1)}${renderJson(
 item[1],
 n + 1
 )}</br>`
 );
 removeList.push('</div>');
 break;
 }
 case '+': {
 // 新增属性
 addlist.push('<div class="error-line-add">');
 addlist.push(
 `<span style="color: red;">${spaceStr.repeat(n + 1)}${renderJson(
 item[1],
 n + 1
 )}</br>`
 );
 addlist.push('</div>');
 break;
 }
 case ' ': {
 // 属性未修改,从originObj中获取,注意对应的index不包括新增的属性
 const index = diffObj.slice(0, i).filter(item => item[0] != '+').length;
 const value = originObj[index];
 const itemStr = `${spaceStr.repeat(n + 1)}${renderJson(value, n + 1)}</br>`;
 addlist.push(itemStr);
 removeList.push(itemStr);
 break;
 }
 }
});
addlist.push(`${spaceStr.repeat(n)}],</br>`);
removeList.push(`${spaceStr.repeat(n)}],</br>`);

renderJson函数,用来格式化json值:

const renderJson = (json, n) => {
 if (!json) {
 return "";
 }
 if (typeof json == "string" || typeof json == "number") {
 return `"${json}",`;
 }
 return `${JSON.stringify(json, null, "\t")
 .replace(new RegExp("\n", "g"), `</br>${spaceStr.repeat(n)}`)
 .replace(new RegExp("\t", "g"), spaceStr.repeat(n))},`;
};

最后对比显示结果如下:

总结

作者:郑丫头

%s 个评论

要回复文章请先登录注册