compare工具的问题在于,如果这些json的节点是未排序,导致基本上显示全部不同。
现node上几行代码,可以输出两个json的不同之处,也可以输出一个所有属性节点有序的json。
注意:这里数组元素也会以对象形式显现,数组的keys即为数组元素index。因为如果按数组处理,需要处理数组的元素是对象的情况,如果数组的元素是对象,那么排序规则是什么?(或者数组应该原样显示?,那么如果是字符串数组,难道不排序下?)
所以没有太完美的解决办法,反正我们的目的是比对和查找,不用于生产,那么代码统一处理带来的简洁可以弥补这点瑕疵。
tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"strict": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"strictNullChecks": true,
"allowJs": true,
"checkJs": true,
"removeComments": true,
"noImplicitAny": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"noImplicitOverride": true,
"sourceMap": true,
"declaration": true,
"rootDir":"./src",
"outDir":"./out",
"experimentalDecorators": true,
"watch":true
},
"include":[
"./src/**/*"
],
"exclude":[
// "./src/test/*"
]
}
package.json
{
"name": "json-sort-diff",
"version": "1.0.0",
"scripts": {
"build": "tsc --build tsconfig.json"
},
"dependencies": {
"@types/node": "^18.11.9"
}
}
json-sort-diff.ts
const fs = require('fs');
(function main() {
const argv = process.argv;
// argv[0] ==> node
// argv[1] ==> the js file
if (argv.length == 3) {
const json = fs.readFileSync(argv[2]);
const obj = JSON.parse(json);
console.log(JSON.stringify(orderedJson(obj), null, 2));
} else if (argv.length === 4) {
const fromJson = fs.readFileSync(argv[2]);
const toJson = fs.readFileSync(argv[3]);
const from = JSON.parse(fromJson);
const to = JSON.parse(toJson);
diff(from, to);
} else {
console.log("Examples:");
console.log("1. Dump sorted json: node json-sort-diff.js path-of-json");
console.log("2. Diff two json: node json-sort-diff.js path-of-from-json path-of-to-json")
process.exit(0);
}
})();
function orderedJson(unordered: any): any {
return Object.keys(unordered).sort().reduce((obj: any, key: string) => {
const value = unordered[key];
if (typeof value === 'object') {
obj[key] = orderedJson(value)
} else {
obj[key] = unordered[key];
}
return obj;
}, {});
}
function dump(obj: any) {
walk(obj, '', (path, key, value, leaf) => {
if (leaf) {
console.log(`${path} -> #(${value})`);
} else {
console.log(path);
}
});
}
function diff(from: any, to: any) {
const noFounds: string[] = [];
const diffInfos: DiffInfo[] = [];
walk(from, '', (path, key, value, leaf) => {
const segments = path.split('/');
if (segments[0] === '') {
segments.shift();
}
let current = to;
for (const segment of segments) {
if (current[segment] === undefined) {
noFounds.push(path);
}
current = current[segment];
}
if (leaf) {
if (current !== value) {
diffInfos.push({ path, from: value, to: current })
}
}
});
console.log('\x1b[33m%s\x1b[0m', 'List of not found:');
for (const noFound of noFounds.sort()) {
console.log(noFound);
}
console.log('\x1b[33m%s\x1b[0m', 'List of different value:');
for (const diffInfo of diffInfos.sort((a, b) => a.path.localeCompare(b.path))) {
console.log(`${diffInfo.path} diff -> from:${diffInfo.from}; to:${diffInfo.to}`);
}
}
export function walk(object: any, path: string, callback: (path: string, key: string, value: any, leaf: boolean) => void) {
for (const [key, value] of Object.entries(object)) {
const current = path + '/' + key;
if (typeof value === 'object') {
callback(current, key, value, false);
walk(value, current, callback);
} else {
callback(current, key, value, true);
}
}
}
interface DiffInfo {
path: string;
from: any;
to: any;
}