Skip to content

Commit f0febdc

Browse files
committed
Merge pull request #3 from rwatler/master
Add factorizing support for converting duplicate add to copy operations
2 parents a9d0bea + 50ac1c9 commit f0febdc

File tree

2 files changed

+98
-8
lines changed

2 files changed

+98
-8
lines changed

src/main/java/com/github/fge/jsonpatch/JsonFactorizingDiff.java

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,9 @@
4949
* </ul>
5050
*
5151
* Array values generate operations in the order of elements. Factorizing is
52-
* done to merge add and remove into move operations if values are equivalent.
53-
* Copy and test operations are not generated.</p>
52+
* done to merge add and remove into move operations and convert duplicate
53+
* add to copy operations if values are equivalent. Test operations are not
54+
* generated.</p>
5455
*
5556
* <p>Note that due to the way {@link JsonNode} is implemented, this class is
5657
* inherently <b>not</b> thread safe (since {@code JsonNode} is mutable). It is
@@ -327,9 +328,11 @@ else if (lengths[x][y] == lengths[x][y - 1])
327328

328329
/**
329330
* Factorize list of ordered differences. Where removed values are
330-
* equivalent to added values, merge add and remove to move operation
331-
* differences. Because remove operation differences are relocated in
332-
* the process of merging, other differences can be side effected.
331+
* equivalent to added values, merge add and remove to move
332+
* differences. Because remove differences are relocated in the
333+
* process of merging, other differences can be side effected.
334+
* Add differences with equivalent values to previous add
335+
* differences are converted to copy differences.
333336
*
334337
* @param diffs list of ordered differences.
335338
*/
@@ -364,7 +367,8 @@ private static void factorizeDiffs(final List<Diff> diffs)
364367
// are performed out of the original diff ordering just before the
365368
// paired add when converted to a move. consequently, moves that are
366369
// deferred or advanced must be tracked to allow proper diff array
367-
// index adjustments for diffs operating on the same arrays.
370+
// index adjustments for diffs operating on the same arrays. diff
371+
// order is assumed to be acyclic and linearly processing arrays.
368372
final List<Diff> deferredArrayRemoves = Lists.newArrayList();
369373
final List<Diff> advancedArrayRemoves = Lists.newArrayList();
370374
for (final Iterator<Diff> diffIter = diffs.iterator();
@@ -445,6 +449,37 @@ private static void factorizeDiffs(final List<Diff> diffs)
445449
diff.secondArrayIndex = adjustSecondArrayIndex(deferredArrayRemoves,
446450
diff.arrayPath, diff.secondArrayIndex);
447451
}
452+
453+
// Factorize add diffs with equivalent non-empty object or array
454+
// values into copy diffs; from paths for copy diffs can be set using
455+
// previous add diff paths and/or array paths because diff order is
456+
// acyclic and immutable for this factorization. The only exception
457+
// to this rule are adds that append to arrays: these have no concrete
458+
// path that can serve as a copy diff from path.
459+
final List<Diff> addDiffs = Lists.newArrayList();
460+
for (final Diff diff: diffs)
461+
if (diff.operation == DiffOperation.ADD)
462+
if (diff.value.size() > 0) {
463+
// check add diff value against list of previous add diffs
464+
Diff addDiff = null;
465+
for (final Diff testAddDiff: addDiffs)
466+
if (EQUIVALENCE.equivalent(diff.value, testAddDiff.value)) {
467+
addDiff = testAddDiff;
468+
break;
469+
}
470+
// if not found previously, save add diff, (if not appending
471+
// to an array which can have no concrete from path), and continue
472+
if (addDiff == null) {
473+
if (diff.arrayPath == null || diff.secondArrayIndex != -1)
474+
addDiffs.add(diff);
475+
continue;
476+
}
477+
// previous add diff found by value: convert add diff to copy
478+
// diff with from path set to concrete add diff path
479+
diff.operation = DiffOperation.COPY;
480+
diff.fromPath = addDiff.arrayPath != null ? addDiff.getSecondArrayPath()
481+
: addDiff.path;
482+
}
448483
}
449484

450485
/**
@@ -512,6 +547,7 @@ private enum DiffOperation
512547
REMOVE("remove"),
513548
REPLACE("replace"),
514549
MOVE("move"),
550+
COPY("copy"),
515551
;
516552

517553
private final String opName;
@@ -577,15 +613,16 @@ private JsonNode asJsonPatch()
577613
: path;
578614
final ObjectNode patch = operation.newOp(ptr);
579615
/*
580-
* A remomve only has a path
616+
* A remove only has a path
581617
*/
582618
if (operation == DiffOperation.REMOVE)
583619
return patch;
584620
/*
585621
* A move has a "source path" (the "from" member), other defined
586622
* operations (add and replace) have a value instead.
587623
*/
588-
if (operation == DiffOperation.MOVE)
624+
if (operation == DiffOperation.MOVE
625+
|| operation == DiffOperation.COPY)
589626
patch.put("from", fromPath.toString());
590627
else
591628
patch.put("value", value);

src/test/resources/jsonpatch/factorizing-diff.json

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,5 +230,58 @@
230230
{ "op": "move", "from": "/b/5", "path": "/b/3" },
231231
{ "op": "move", "from": "/b/0", "path": "/b/6" }
232232
]
233+
},
234+
{
235+
"first": { "b": [0, 1, 2, 3, 4, 5, 6, 7, 8] },
236+
"second": { "b": [1, 3, 6, 4, 5, 7, 0, 8], "c": 2 },
237+
"patch": [
238+
{ "op": "move", "from": "/b/2", "path": "/c" },
239+
{ "op": "move", "from": "/b/5", "path": "/b/3" },
240+
{ "op": "move", "from": "/b/0", "path": "/b/6" }
241+
]
242+
},
243+
{
244+
"first": {},
245+
"second": { "a": 1, "b": 1},
246+
"patch": [
247+
{ "op": "add", "path": "/a", "value": 1 },
248+
{ "op": "add", "path": "/b", "value": 1 }
249+
]
250+
},
251+
{
252+
"first": {},
253+
"second": { "a": {}, "b": {}},
254+
"patch": [
255+
{ "op": "add", "path": "/a", "value": {} },
256+
{ "op": "add", "path": "/b", "value": {} }
257+
]
258+
},
259+
{
260+
"first": {},
261+
"second": { "a": { "a": 1 }, "b": { "a": 1.0 }},
262+
"patch": [
263+
{ "op": "add", "path": "/a", "value": { "a": 1 } },
264+
{ "op": "copy", "from": "/a", "path": "/b" }
265+
]
266+
},
267+
{
268+
"first": [],
269+
"second": [ [ 0 ], [ 0 ] ],
270+
"patch": [
271+
{ "op": "add", "path": "/-", "value": [ 0 ] },
272+
{ "op": "add", "path": "/-", "value": [ 0 ] }
273+
]
274+
},
275+
{
276+
"first": [ "eol" ],
277+
"second": [ { "a": 1 }, { "a": 1.0 }, [], [], [ 0 ], [ 0 ], "eol" ],
278+
"patch": [
279+
{ "op": "add", "path": "/0", "value": { "a": 1 } },
280+
{ "op": "copy", "from": "/0", "path": "/1" },
281+
{ "op": "add", "path": "/2", "value": [] },
282+
{ "op": "add", "path": "/3", "value": [] },
283+
{ "op": "add", "path": "/4", "value": [ 0 ] },
284+
{ "op": "copy", "from": "/4", "path": "/5" }
285+
]
233286
}
234287
]

0 commit comments

Comments
 (0)