Skip to content

Commit 69c7f0a

Browse files
Qix-kpdecker
authored andcommitted
Accept a custom JSON replacer function for JSON diffing
1 parent 2aec429 commit 69c7f0a

File tree

2 files changed

+56
-12
lines changed

2 files changed

+56
-12
lines changed

src/diff/json.js

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,9 @@ jsonDiff.useLongestToken = true;
1111

1212
jsonDiff.tokenize = lineDiff.tokenize;
1313
jsonDiff.castInput = function(value) {
14-
const {undefinedReplacement} = this.options;
14+
const {undefinedReplacement, stringifyReplacer = (k, v) => typeof v === 'undefined' ? undefinedReplacement : v} = this.options;
1515

16-
return typeof value === 'string' ? value : JSON.stringify(canonicalize(value), function(k, v) {
17-
if (typeof v === 'undefined') {
18-
return undefinedReplacement;
19-
}
20-
21-
return v;
22-
}, ' ');
16+
return typeof value === 'string' ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), stringifyReplacer, ' ');
2317
};
2418
jsonDiff.equals = function(left, right) {
2519
return Diff.prototype.equals.call(jsonDiff, left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1'));
@@ -28,11 +22,15 @@ jsonDiff.equals = function(left, right) {
2822
export function diffJson(oldObj, newObj, options) { return jsonDiff.diff(oldObj, newObj, options); }
2923

3024
// This function handles the presence of circular references by bailing out when encountering an
31-
// object that is already on the "stack" of items being processed.
32-
export function canonicalize(obj, stack, replacementStack) {
25+
// object that is already on the "stack" of items being processed. Accepts an optional replacer
26+
export function canonicalize(obj, stack, replacementStack, replacer, key) {
3327
stack = stack || [];
3428
replacementStack = replacementStack || [];
3529

30+
if (replacer) {
31+
obj = replacer(key, obj);
32+
}
33+
3634
let i;
3735

3836
for (i = 0; i < stack.length; i += 1) {
@@ -48,7 +46,7 @@ export function canonicalize(obj, stack, replacementStack) {
4846
canonicalizedObj = new Array(obj.length);
4947
replacementStack.push(canonicalizedObj);
5048
for (i = 0; i < obj.length; i += 1) {
51-
canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack);
49+
canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack, replacer, key);
5250
}
5351
stack.pop();
5452
replacementStack.pop();
@@ -74,7 +72,7 @@ export function canonicalize(obj, stack, replacementStack) {
7472
sortedKeys.sort();
7573
for (i = 0; i < sortedKeys.length; i += 1) {
7674
key = sortedKeys[i];
77-
canonicalizedObj[key] = canonicalize(obj[key], stack, replacementStack);
75+
canonicalizedObj[key] = canonicalize(obj[key], stack, replacementStack, replacer, key);
7876
}
7977
stack.pop();
8078
replacementStack.pop();

test/diff/json.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ describe('diff/json', function() {
1515
{ count: 1, value: '}' }
1616
]);
1717
});
18+
1819
it('should accept objects with different order', function() {
1920
expect(diffJson(
2021
{a: 123, b: 456, c: 789},
@@ -136,6 +137,51 @@ describe('diff/json', function() {
136137
expect(getKeys(canonicalObj)).to.eql(['a', 'b']);
137138
expect(getKeys(canonicalObj.a)).to.eql(['a', 'b']);
138139
});
140+
141+
it('should accept a custom JSON.stringify() replacer function', function() {
142+
expect(diffJson(
143+
{a: 123},
144+
{a: /foo/}
145+
)).to.eql([
146+
{ count: 1, value: '{\n' },
147+
{ count: 1, value: ' \"a\": 123\n', added: undefined, removed: true },
148+
{ count: 1, value: ' \"a\": {}\n', added: true, removed: undefined },
149+
{ count: 1, value: '}' }
150+
]);
151+
152+
expect(diffJson(
153+
{a: 123},
154+
{a: /foo/gi},
155+
{stringifyReplacer: (k, v) => v instanceof RegExp ? v.toString() : v}
156+
)).to.eql([
157+
{ count: 1, value: '{\n' },
158+
{ count: 1, value: ' \"a\": 123\n', added: undefined, removed: true },
159+
{ count: 1, value: ' \"a\": "/foo/gi"\n', added: true, removed: undefined },
160+
{ count: 1, value: '}' }
161+
]);
162+
163+
expect(diffJson(
164+
{a: 123},
165+
{a: new Error('ohaider')},
166+
{stringifyReplacer: (k, v) => v instanceof Error ? `${v.name}: ${v.message}` : v}
167+
)).to.eql([
168+
{ count: 1, value: '{\n' },
169+
{ count: 1, value: ' \"a\": 123\n', added: undefined, removed: true },
170+
{ count: 1, value: ' \"a\": "Error: ohaider"\n', added: true, removed: undefined },
171+
{ count: 1, value: '}' }
172+
]);
173+
174+
expect(diffJson(
175+
{a: 123},
176+
{a: [new Error('ohaider')]},
177+
{stringifyReplacer: (k, v) => v instanceof Error ? `${v.name}: ${v.message}` : v}
178+
)).to.eql([
179+
{ count: 1, value: '{\n' },
180+
{ count: 1, value: ' \"a\": 123\n', added: undefined, removed: true },
181+
{ count: 3, value: ' \"a\": [\n "Error: ohaider"\n ]\n', added: true, removed: undefined },
182+
{ count: 1, value: '}' }
183+
]);
184+
});
139185
});
140186
});
141187

0 commit comments

Comments
 (0)