forked from kpdecker/jsdiff
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathjson.ts
More file actions
130 lines (114 loc) · 4.45 KB
/
Copy pathjson.ts
File metadata and controls
130 lines (114 loc) · 4.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import Diff from './base.js';
import type { ChangeObject, CallbackOptionAbortable, CallbackOptionNonabortable, DiffCallbackNonabortable, DiffJsonOptionsAbortable, DiffJsonOptionsNonabortable} from '../types.js';
import { tokenize } from './line.js';
class JsonDiff extends Diff<string, string, string | object> {
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;
}
tokenize = tokenize;
castInput(value: string | object, options: DiffJsonOptionsNonabortable | DiffJsonOptionsAbortable) {
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: string, right: string, options: DiffJsonOptionsNonabortable | DiffJsonOptionsAbortable) {
return super.equals(left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1'), options);
}
}
export const jsonDiff = new JsonDiff();
/**
* diffs two JSON-serializable objects by first serializing them to prettily-formatted JSON and then treating each line of the JSON as a token.
* Object properties are ordered alphabetically in the serialized JSON, so the order of properties in the objects being compared doesn't affect the result.
*
* @returns a list of change objects.
*/
export function diffJson(
oldStr: string | object,
newStr: string | object,
options: DiffCallbackNonabortable<string>
): undefined;
export function diffJson(
oldStr: string | object,
newStr: string | object,
options: DiffJsonOptionsAbortable & CallbackOptionAbortable<string>
): undefined
export function diffJson(
oldStr: string | object,
newStr: string | object,
options: DiffJsonOptionsNonabortable & CallbackOptionNonabortable<string>
): undefined
export function diffJson(
oldStr: string | object,
newStr: string | object,
options: DiffJsonOptionsAbortable
): ChangeObject<string>[] | undefined
export function diffJson(
oldStr: string | object,
newStr: string | object,
options?: DiffJsonOptionsNonabortable
): ChangeObject<string>[]
export function diffJson(oldStr: string | object, newStr: string | object, options?: any): undefined | ChangeObject<string>[] {
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: any,
stack: Array<any> | null, replacementStack: Array<any> | null,
replacer: (k: string, v: any) => any,
key?: string
) {
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: any;
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 && typeof obj.toJSON === 'function') {
obj = obj.toJSON();
}
if (typeof obj === 'object' && obj !== null) {
stack.push(obj);
// Use a null-prototype object so that an own "__proto__" key is stored as
// a normal property instead of triggering the Object.prototype setter,
// which would silently drop the key from the canonicalized output.
canonicalizedObj = Object.create(null);
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;
}