如何通過(guò)遞歸方法實(shí)現(xiàn)用json-diff渲染json字符串對(duì)比結(jié)果
前言
上一篇,對(duì)比了js-diff和json-diff,發(fā)現(xiàn)js-diff對(duì)比的結(jié)果返回的是拆分后字符串并且字符串中帶有換行符跟空格,將拆分后的字符串拼接可以得到完整的兩個(gè)json格式化后的信息。但是json-diff只返回了兩個(gè)json中有修改的的部分,并且返回類型還要根據(jù)值類型來(lái)進(jìn)行判斷。
分析json-diff的結(jié)構(gòu)
用兩個(gè)數(shù)組嵌套對(duì)象,對(duì)象嵌套數(shù)組的比較復(fù)雜的json進(jìn)行對(duì)比,獲得的數(shù)據(jù)如下:
var json1 = {
name: '小明',
age: '22',
hobby: ['籃球', '足球', '羽毛球'],
grade: {
English: ['130', '129', '135'],
Chinese: ['120', '118', '122'],
sports: ['90', '90', '90'],
},
honor: [{ desc: '獲得數(shù)學(xué)競(jìng)賽第一名', 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": "獲得數(shù)學(xué)競(jìng)賽第一名",
"__new": "獲得作文比賽第一名"
},
"info": [
[
"~",
{
"ranking__deleted": 1,
"rank__added": 2,
"score": {
"__old": "100",
"__new": "99"
}
}
],
[
" "
],
[
" "
]
]
}
]
]
};可以觀察到:
- 字符串返回{"__old":string,"__new":string},數(shù)組返回[string[,string][,object]],非數(shù)組對(duì)象返回{ "key__added": string|object, "key__deleted": string|object, "key": string|object }。
- 字符串通過(guò)__old、__new獲取
- 非數(shù)組對(duì)象新增屬性從key__added獲取,刪除屬性從key__deleted獲取
- 數(shù)組第一個(gè)值有四種類型空格表示未修改、+表示新增、-表示刪除、~表示修改
解析結(jié)構(gòu)圖如下:

太繞了,畫(huà)圖之后才看的明白一點(diǎn)ORZ...
用遞歸方法拼接json字符串
首先創(chuàng)建一個(gè)函數(shù)用來(lái)判斷json是否為數(shù)組,renderobj(diffObj, originObj, n),diffObj表示當(dāng)層的diff對(duì)象,originObj表示當(dāng)層的json1對(duì)象,n表示深度,第一層深度為0,主要為了方便計(jì)算縮進(jìn)。
renderobj函數(shù):
// 定義remove和add隊(duì)列
const removeList = [];
const addlist = [];
if (diffObj instanceof Array) {
// 數(shù)組的判斷
} else if (typeof diffObj == 'object'){
if (diffObj.__new) {
// 字符串修改
} else {
// 非數(shù)組對(duì)象修改
}
}字符串修改
當(dāng)diffObj.__new為真時(shí),__old同時(shí)有值,表示字符串做修改,此時(shí)可以直接把__new放到addList,__old存入removeList。
addlist.push(`<span style="color:red;">"${diffObj.__new}"</span>,<br/>`);
removeList.push(`<span style="color:red;">"${diffObj.__old}"</span>,<br/>`);非數(shù)組對(duì)象修改
const { addHtml, removeHtml } = renderObject(diffObj, originObj, n);
addlist.push(addHtml);
removeList.push(removeHtml);非數(shù)組對(duì)象要判斷key的值,創(chuàng)建函數(shù)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?因?yàn)閖son1對(duì)象中沒(méi)有新增的key,diffObj中沒(méi)有返回未修改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) {
// 當(dāng)字符串修改時(shí),將整行標(biāo)紅
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 {
// 沒(méi)修改的屬性直接存入
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中沒(méi)有,從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>');
}
});數(shù)組對(duì)象修改
遍歷diffObj,判斷item[0]的值,當(dāng)值為空格時(shí),需要從json1中獲取原屬性值,diffObj中空格所在的索引位置有可能和originObj中索引的位置不同,要排除掉'+'新增的成員;當(dāng)值為'+'時(shí),直接將值存入addList;當(dāng)值為'-'時(shí),直接將值存入removeList;當(dāng)值為'~'時(shí),表示有修改,修改類型不確定需要重新循環(huán)判斷。
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中獲取,注意對(duì)應(yīng)的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函數(shù),用來(lái)格式化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))},`;
};最后對(duì)比顯示結(jié)果如下:

總結(jié)
到此這篇關(guān)于如何通過(guò)遞歸方法實(shí)現(xiàn)用json-diff渲染json字符串對(duì)比結(jié)果的文章就介紹到這了,更多相關(guān)json-diff渲染json字符串對(duì)比結(jié)果內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
gridpanel動(dòng)態(tài)加載數(shù)據(jù)的實(shí)例代碼
這篇文章介紹了gridpanel動(dòng)態(tài)加載數(shù)據(jù)的實(shí)例代碼,有需要的朋友可以參考一下2013-07-07
JS前端實(shí)現(xiàn)留言板功能的方法總結(jié)
留言板的主要使用場(chǎng)景是為用戶提供一個(gè)在網(wǎng)站或應(yīng)用上留言的平臺(tái),本文主要為大家介紹了四個(gè)常見(jiàn)的前端實(shí)現(xiàn)留言板功能的方法,希望對(duì)大家有所幫助2023-11-11
使用OpenLayers3 添加地圖鼠標(biāo)右鍵菜單
這篇文章主要介紹了使用OpenLayers3 添加地圖鼠標(biāo)右鍵菜單的相關(guān)資料,需要的朋友可以參考下2015-12-12
小程序hover-class點(diǎn)擊態(tài)效果實(shí)現(xiàn)
這篇文章主要介紹了小程序hover-class點(diǎn)擊態(tài)效果實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-02-02
js將當(dāng)前時(shí)間格式化為 年-月-日 時(shí):分:秒的實(shí)現(xiàn)代碼
這篇文章主要介紹了js將當(dāng)前時(shí)間格式化為 年-月-日 時(shí):分:秒主要是使用js的Date()對(duì)象,將系統(tǒng)當(dāng)前時(shí)間格式化為年-月-日 時(shí):分:秒,需要的朋友可以參考下2018-01-01
點(diǎn)擊單元格后可編輯單元格內(nèi)文本如何制作
點(diǎn)擊單元格后可編輯單元格內(nèi)文本如何制作...2006-10-10
ionic中的$ionicPlatform.ready事件中的通用設(shè)置
$ionicPlatform.ready事件是用于檢測(cè)當(dāng)前的平臺(tái)是否就緒的事件,相當(dāng)于基于document的deviceready事件, 在app中一些通用關(guān)于設(shè)備的設(shè)置必須在這個(gè)事件中處理2017-06-06
layui 實(shí)現(xiàn)加載動(dòng)畫(huà)以及非真實(shí)加載進(jìn)度的方法
今天小編就為大家分享一篇layui 實(shí)現(xiàn)加載動(dòng)畫(huà)以及非真實(shí)加載進(jìn)度的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-09-09
微信小程序記住密碼的功能簡(jiǎn)單幾步實(shí)現(xiàn)
軟件中的“記住密碼”選框不知道大家平時(shí)會(huì)不會(huì)勾選,反正對(duì)于一個(gè)重度懶癌患者的我來(lái)說(shuō)就沒(méi)有不勾選的時(shí)候,畢竟隔一段時(shí)間就重新輸入一遍難記又難輸?shù)馁~號(hào)密碼,想想就讓人頭皮發(fā)麻。今天教大家用代碼在微信小程序中實(shí)現(xiàn)這個(gè)簡(jiǎn)單的小功能2023-01-01
使用js實(shí)現(xiàn)單鏈解決前端隊(duì)列問(wèn)題的方法
這篇文章主要介紹了使用js實(shí)現(xiàn)單鏈解決前端隊(duì)列問(wèn)題的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02

