78 lines
2.8 KiB
JavaScript
78 lines
2.8 KiB
JavaScript
import Diff from './base.js';
|
|
import { tokenize } from './line.js';
|
|
class JsonDiff extends Diff {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.tokenize = tokenize;
|
|
}
|
|
get useLongestToken() {
|
|
// Discriminate between two lines of pretty-printed, serialized JSON where one of them has a
|
|
// dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output:
|
|
return true;
|
|
}
|
|
castInput(value, options) {
|
|
const { undefinedReplacement, stringifyReplacer = (k, v) => typeof v === 'undefined' ? undefinedReplacement : v } = options;
|
|
return typeof value === 'string' ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), null, ' ');
|
|
}
|
|
equals(left, right, options) {
|
|
return super.equals(left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1'), options);
|
|
}
|
|
}
|
|
export const jsonDiff = new JsonDiff();
|
|
export function diffJson(oldStr, newStr, options) {
|
|
return jsonDiff.diff(oldStr, newStr, options);
|
|
}
|
|
// This function handles the presence of circular references by bailing out when encountering an
|
|
// object that is already on the "stack" of items being processed. Accepts an optional replacer
|
|
export function canonicalize(obj, stack, replacementStack, replacer, key) {
|
|
stack = stack || [];
|
|
replacementStack = replacementStack || [];
|
|
if (replacer) {
|
|
obj = replacer(key === undefined ? '' : key, obj);
|
|
}
|
|
let i;
|
|
for (i = 0; i < stack.length; i += 1) {
|
|
if (stack[i] === obj) {
|
|
return replacementStack[i];
|
|
}
|
|
}
|
|
let canonicalizedObj;
|
|
if ('[object Array]' === Object.prototype.toString.call(obj)) {
|
|
stack.push(obj);
|
|
canonicalizedObj = new Array(obj.length);
|
|
replacementStack.push(canonicalizedObj);
|
|
for (i = 0; i < obj.length; i += 1) {
|
|
canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack, replacer, String(i));
|
|
}
|
|
stack.pop();
|
|
replacementStack.pop();
|
|
return canonicalizedObj;
|
|
}
|
|
if (obj && obj.toJSON) {
|
|
obj = obj.toJSON();
|
|
}
|
|
if (typeof obj === 'object' && obj !== null) {
|
|
stack.push(obj);
|
|
canonicalizedObj = {};
|
|
replacementStack.push(canonicalizedObj);
|
|
const sortedKeys = [];
|
|
let key;
|
|
for (key in obj) {
|
|
/* istanbul ignore else */
|
|
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
sortedKeys.push(key);
|
|
}
|
|
}
|
|
sortedKeys.sort();
|
|
for (i = 0; i < sortedKeys.length; i += 1) {
|
|
key = sortedKeys[i];
|
|
canonicalizedObj[key] = canonicalize(obj[key], stack, replacementStack, replacer, key);
|
|
}
|
|
stack.pop();
|
|
replacementStack.pop();
|
|
}
|
|
else {
|
|
canonicalizedObj = obj;
|
|
}
|
|
return canonicalizedObj;
|
|
}
|