使用js-diff渲染json比对结果,并标红


theme: fancy

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情

前言

最近来了一个需求,用js比对两个json字符串之间的差异,并用明显的颜色标示出来。一开始这个需求领导是丢给后端的,后端试了一圈之后,表示不太行于是这锅又甩了回来。行吧,心痛的接下,结果找了一圈都是比对json文件的编辑器,网上大部分给出的办法就是使用diffJs加上自渲染去展示。然而,自渲染还是有一定难度的,坑大好磕。

image.png

难点:

  • 要格式化json字符串,该换行的换行,该留空格的要留空格。
  • json深度越大,层级越多越难,很难自己通过js去判断两个json的区别。
  • 要注意标红的行和字要如何处理

对比两种diffJs方法

网上找到两种diff工具json-diff和js-diff,简单尝试了下,这两种js的处理的数据有一些区别:

var json1 = { label: '11', value: '22', arr: [1, 2, 3], obj: { label: '11' } }
var json2 = { label: '112', value: '22', arr: [1, 4, 5], list: [1, 1] }

js-diff比对获取到的数据:

var jsDiff = [
    {
        "count": 3,
        "value": "{\n  \"arr\": [\n    1,\n"
    },
    {
        "count": 2,
        "removed": true,
        "value": "    2,\n    3\n"
    },
    {
        "count": 2,
        "added": true,
        "value": "    4,\n    5\n"
    },
    {
        "count": 1,
        "value": "  ],\n"
    },
    {
        "count": 4,
        "removed": true,
        "value": "  \"label\": \"11\",\n  \"obj\": {\n    \"label\": \"11\"\n  },\n"
    },
    {
        "count": 5,
        "added": true,
        "value": "  \"label\": \"112\",\n  \"list\": [\n    1,\n    1\n  ],\n"
    },
    {
        "count": 2,
        "value": "  \"value\": \"22\"\n}"
    }
]

json-diff比对获取的数据:

var jsonDiff = {
    "obj__deleted": {
        "label": "11"
    },
    "list__added": [
        1,
        1
    ],
    "label": {
        "__old": "11",
        "__new": "112"
    },
    "arr": [
        [
            " "
        ],
        [
            "-",
            2
        ],
        [
            "-",
            3
        ],
        [
            "+",
            4
        ],
        [
            "+",
            5
        ]
    ]
};

很明显的可以看出,js-diff比较符合要求,即带有换行,而且比对出来的信息比json-diff完整。

  • js-diff只有三种状态,removed、added、value。removed表示json1移除内容,added表示json2新增内容,value表示值没有修改;
  • 获取removed和added两种状态的value值,可以得到完整的json1和json2;

json-diff就比较复杂了,而且返回的信息中不包括匹配的信息。

  • 根据不同对象有不同的返回结果。
  • 数组对象返回数组,数组内每个数组有一个或两个长度[string[,string|object]],第一个字符表示匹配状态,” “为空格时数组没有第二个值,表示这个json中当前索引位置的值没有修改,”-“表示删除值,”+”表示新增值,当状态为’-‘/’+’的时候,第二个索引值返回修改的值;
  • 其他对象返回{}对象,返回的key是json中有修改值的key,key的值中”__old”是修改前的值,”__new”是修改后的值,没有修改的key不返回,新增的key从key__added中获取,删除的key从key__deleted中获取。
  • 好复杂啊啊啊

对比之下,最后还是选择js-diff来处理。

使用js-diff渲染html

要点:

  • 根据removed和added标记修改的块,同时还要使用jsDiff.diffWordsWithSpace标注修改的具体字符
  • 新增行添加样式error-line-add,删除行添加样式error-line-remove
//获取对比后的结果及拼接展示效果
const diffJSON = (json1, json2) => {
    // diff之后的结果
    const diffResult = jsDiff.diffJson(json1, json2);
    let addedList = [];
    let removedList = [];
    //遍历对比结果,摘出相应的数据
    //首先判断的量级为一行
    diffResult.forEach((diffObj, index) => {
      let content = diffObj.value;
      let replaceContent = replaceContentFunc(content);
      //该项的下一项
      let next = diffResult[index + 1];
      let nextReplceContent = next && replaceContentFunc(next.value);
      if (diffObj.removed) {
        //需要展示在历史结果位置的数据
        let dealedContent = '';
        if (diffObj.removeContent) {
          dealedContent = diffObj.removeContent;
        } else {
          //判断下一个项
          if (next && next.added) {
            //说明两个项之前是对应的关系
            //判断在不同的这行中,具体到字段的不同位置
            let testDiffList = jsDiff.diffWordsWithSpace(replaceContent, nextReplceContent);
            let { addContent, removeContent } = diffText(testDiffList);
            next.addContent = addContent;
            dealedContent = removeContent;
          } else {
            dealedContent =
              '<div class="error-line-remove" style="color:red"">' +
              replaceContent.replace(/\s/gm, '&ensp;') +
              '</div>';
          }
          removedList.push(dealedContent);
        }
      } else if (diffObj.added) {
        //需要展示在当前结果位置的数据
        let dealedContent = '';
        if (diffObj.addContent) {
          dealedContent = diffObj.addContent;
        } else {
          //判断下一个项
          if (next && next.removed) {
            //说明两个项之前是对应的关系
            //判断在不同的这行中,具体到字段的不同位置
            let testDiffList = jsDiff.diffWordsWithSpace(replaceContent, nextReplceContent);
            let { addContent, removeContent } = diffText(testDiffList);
            next.removeContent = removeContent;
            dealedContent = addContent;
          } else {
            dealedContent =
              '<div class="error-line-add" style="color:red">' +
              replaceContent.replace(/\s/gm, '&ensp;') +
              '</div>';
          }
        }
        addedList.push(dealedContent);
      } else {
        //没有改动的部分
        replaceContent = replaceContent.replace(/\s/gm, '&ensp;');
        addedList.push('<div>' + replaceContent + '</div>');
        removedList.push('<div>' + replaceContent + '</div>');
      }
    });
    let addedHtml = addedList.join('');
    let removedHtml = removedList.join('');
    return {
      addedHtml,
      removedHtml,
    };
};
//对比行内容差异并且拼接结果
 const diffText = result => {
    // json2 当前数据
    let addList = [];
    // json1 原始数据
    let removeList = [];
    result.forEach(item => {
      let value = item.value.replace(/\s/gm, '&ensp;');
      if (item.added) {
        addList.push('<span style="color:red">' + value + '</span>');
      } else if (item.removed) {
        removeList.push('<span  style="color:red">' + value + '</span>');
      } else {
        removeList.push('<span>' + value + '</span>');
        addList.push('<span>' + value + '</span>');
      }
    });
    return {
      addContent: `<div class="error-line-add">${addList.join('')}</div>`,
      removeContent: `<div class="error-line-remove">${removeList.join('')}</div>`,
    };
};
//处理换行和空格
const replaceContentFunc = content => {
    let text = content;
    if (content.indexOf('\n') >= 0) {
      // 换行替换为<br/>
      // 空格替换为&ensp;
      const reg1 = new RegExp('\n', 'g');
      text = text.replace(reg1, '<br/>');
    }
    return text;
};
.error-line-remove {
  background-color: rgb(248, 216, 216);
  position: relative;

  &::before {
    content: '-';
    position: absolute;
    left: 5px;
    color: black;
    line-height: 19.5px;
  }
}

.error-line-add {
  background-color: rgb(226, 248, 216);
  position: relative;

  &::before {
    content: '+';
    position: absolute;
    left: 5px;
    color: black;
    line-height: 19.5px;
  }
}

最后处理的样式如下:

image.png

最后,这篇还是有参考代码的,json-diff没有…爱学习的我顺便也把json-diff的渲染也给写出来了,用递归写的,给我绕的(脑壳不够用),下一篇我会发出来,大家自己也可以尝试下。ORZ…拜拜

© 版权声明
THE END
喜欢就支持一下吧
点赞7 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容