如何通过递归方法实现用json-diff渲染json字符串对比结果
郑丫头 人气:0前言
上一篇,对比了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))},`; };
最后对比显示结果如下:
总结
加载全部内容