前言
在一些场景中会出现需要对两个JSON
数据进行比较,并标注对应的增加、删除、修改处。本文基于TypeScript
实现JSON-Diff
方法,用于处理通用场景。
实现功能
由于需要将不同变化情况的数据进行不同的标注表示,所以先初步定义以下功能:
- 比较两个
JSON
数据,并返回发生变化的key
。 - 深度比较内部嵌套,并返回
json[key][key]
的形式。 - 深度比较「字符串/数组」类型数据,并返回内部修改的数据索引。
1. 定义接口
首先定义最内层的数据结构,也就是diff
后的结果。
interface DiffDetail {
'old': any,
'new': any
}
对比后分别返回old
和new
的结果。由于当数据内容为String
时,需要深度遍历内部变化,因此在外层设置一个DiffData
用于包裹DiffDetail
。
interface DiffData {
// 是否需要深度遍历字符串等
deepType?: String,
// diff的两个数据类型
type?: String,
// diff结果,字符串为DiffData,否则为DiffDetail
detail?: DiffDetail | DiffData
}
当遍历到字符串时,再包裹一层DiffData
用于展示字符串内部的变化情况。
最后定义最终返回的结果。
interface Result {
readonly type: String,
data: DiffData,
success: Boolean,
errorMsg?: String
}
其中data
存放diff
后的结果。
2. 使用方法
定义两个简单的json
。
const json1 = {
"a": 1,
"b": 2,
"e": 0
}
const json2 = {
"a": 1,
"b": 3,
"c": 4
}
调用diff
方法后返回数据为
Result {
"type": "Object",
"data": {
"b": {
"type": "modify",
"detail": {
"old": 2,
"new": 3
}
},
"e": {
"type": "delete",
"detail": {
"old": 0,
"new": null
}
},
"c": {
"type": "add",
"detail": {
"old": null,
"new": 4
}
}
},
"success": true
}
只需要判断对应Result
中是否存在对应的key
,就知道是否发生变化,其中返回的type
也可以作为DOM
使用的类名。
3. 基本思路
针对两个json
数据的比较,由于存在前者的数据后者没有的情况,或后者新增某项值。此时想到的是合并两个json
再进行比较,但是合并的过程又需要递归,效率较低。
因此可以遍历其中一个json
,如遍历json1
当其中的key
在json2
中找不到时,代表数据被删除。当key
能找到,代表数据既不增加也不删除,此时在json2
中删除对应的key
。遍历完成后json2
中剩余的即新增的key
。
4. 定义Diff
方法
4.1 数据校验
首先在最外层做数据的校验,校验后进行遍历比较。
/**
* Diff
* @param payload1 {array | object}
* @param payload2 {array | object}
* @returns
* Object {
* [key | index]: {
* type: modify | add | delete,
* detail: {
* old: any,
* new: any
* }
* }
* }
*/
function Diff(payload1: Array<any> | Object, payload2: Array<any> | Object): Object {
const result: Result = {
type: getInstance(payload2),
data: {},
success: true
};
// 判断基本类型不同
if (typeof payload1 !== typeof payload2) {
result.success = false;
result.errorMsg = '数据基本类型不同';
return result;
}
// 判断引用类型不同
if (!equalType(payload1, payload2)) {
result.success = false;
result.errorMsg = '数据类型不同';
return result;
}
diffObject(result.data, payload1, payload2);
return result;
}
其中定义了两个简单的工具函数getInstance
和equalType
。
/**
* 判断属于相同的类型
* @param obj1 {Object}
* @param obj2 {Object}
* @returns Boolean
*/
function equalType(obj1: Object, obj2: Object): Boolean {
if (typeof obj1 !== 'object' && typeof obj1 !== 'function') return false;
return getInstance(obj1) === getInstance(obj2);
}
/**
* 获取引用数据具体类型字符串
* @param obj {any}
* @return String
*/
function getInstance(obj: any): String {
if (typeof obj !== 'object' && typeof obj !== 'function') return typeof obj;
const _toString: Function = Object.prototype.toString;
return _toString.call(obj).slice(8, -1);
}
校验数据后开始diffObject
函数。
4.2 diffObject
定好基本思路后,diffObject
方法只需要实现对应的循环和递归即可。
遍历过程中共有以下几种情况分别进行处理:
- 「删除」
json2
中不存在对应key
- 「修改并递归」
json1
与json2
均不为基本数据类型,且数据结构相同 - 「修改并递归」
json1
与json2
均为String
类型 - 「修改」
json1
与json2
数据结构不同或值不同 - 「不变」
json1
与json2
相同 - 「新增」
json2
中剩余的key
最终完成整理代码逻辑如下:
/**
* diff - Object类型数据
* @param result {object} diff结果对象
* @param payload1 {object}
* @param payload2 {object}
*/
function diffObject(result: Object, payload1: Object, payload2: Object): void {
// 深拷贝payload2,避免影响原数据
const _obj2: Object = deepClone(payload2);
for (let key in payload1) {
// json2中不存在时代表删除属性
if (!_obj2[key]) {
result[key] = {
type: 'delete',
detail: {
old: payload1[key],
new: null
}
};
continue;
}
// 数据结构相同且不为基本数据类型时
if (equalType(payload1[key], payload2[key])) {
result[key] = result[key] ? result[key] : {};
// 递归子属性
diffObject(result[key], payload1[key], payload2[key]);
// 删除对应json2上的key
delete _obj2[key];
continue;
}
// 数据类型为字符串且长度大于1时深度diff字符串
if (payload1[key] !== payload2[key] && typeof payload1[key] === 'string' && typeof payload2[key] === 'string' && (payload1[key].length > 1 || payload2[key].length > 1)) {
result[key] = result[key] ? result[key] : {
deepType: 'String',
type: 'modify',
detail: {}
};
// 将字符串转为数组并递归
diffObject(result[key].detail, payload1[key].split(''), payload2[key].split(''));
// 删除对应json2上的key
delete _obj2[key];
continue;
}
// 基本类型直接比较是否不同
if (payload2[key] && (payload1[key] !== payload2[key])) {
result[key] = {
type: 'modify',
detail: {
old: payload1[key],
new: payload2[key]
}
};
// 删除对应json2上的key
delete _obj2[key];
continue;
}
// 相同情况,删除json2中的key
delete _obj2[key];
}
// json2剩余的均为新增
for (let key in _obj2) {
result[key] = {
type: 'add',
detail: {
old: null,
new: payload2[key]
}
};
}
}
至此,整个Diff
函数完成,测试结果与预期一致。
5. 测试
输入两个json
// json1
{
"a": "test1",
"b": "test2",
"e": 0,
"x": {
"y": 12
}
}
// json2
{
"a": 1,
"b": "test3",
"c": 4,
"x": {
"y": 13
}
}
调用Diff(json1, json2)
后得到的结果为
Result {
"type": "Object",
"data": {
"a": {
"type": "modify",
"detail": {
"old": "test1",
"new": 1
}
},
"b": {
"deepTpye": "String",
"type": "modify",
"detail": {
"4": {
"type": "modify",
"detail": {
"old": "2",
"new": "3"
}
}
}
},
"e": {
"type": "delete",
"detail": {
"old": 0,
"new": null
}
},
"x": {
"y": {
"type": "modify",
"detail": {
"old": 12,
"new": 13
}
}
},
"c": {
"type": "add",
"detail": {
"old": null,
"new": 4
}
}
},
"success": true
}
大致覆盖了一些基本情况,有其他测试用例的小伙伴可以帮忙找出bug
,文章中有哪些错误的内容也欢迎大家及时指正。