Skip to content

Commit c519889

Browse files
authored
Merge pull request #232 from aryelu/master
jsonpatch formatter array move generates incorrect patch - #230
2 parents 5c4cb8e + 522c03c commit c519889

File tree

3 files changed

+143
-27
lines changed

3 files changed

+143
-27
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ dist
1818
build
1919

2020
npm-debug.log
21+
.idea/

src/formatters/jsonpatch.js

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,23 @@ class JSONFormatter extends BaseFormatter {
3030
};
3131

3232
context.pushMoveOp = function(to) {
33-
const finalTo = `/${to}`;
3433
const from = this.currentPath();
35-
this.result.push({op: OPERATIONS.move, from: from, path: finalTo});
34+
this.result.push({
35+
op: OPERATIONS.move,
36+
from: from,
37+
path: this.toPath(to),
38+
});
3639
};
3740

3841
context.currentPath = function() {
3942
return `/${this.path.join('/')}`;
4043
};
44+
45+
context.toPath = function(toPath) {
46+
const to = this.path.slice();
47+
to[to.length - 1] = toPath;
48+
return `/${to.join('/')}`;
49+
};
4150
}
4251

4352
typeFormattterErrorFormatter(context, err) {
@@ -127,31 +136,29 @@ const opsByDescendingOrder = removeOps => sortBy(removeOps, (a, b) => {
127136
}
128137
});
129138

130-
const partition = (arr, pred) => {
131-
const left = [];
132-
const right = [];
133-
134-
arr.forEach(el => {
135-
const coll = pred(el) ? left : right;
136-
coll.push(el);
137-
});
138-
return [left, right];
139-
};
140-
141-
const partitionRemovedOps = jsonFormattedDiff => {
142-
const isRemoveOp = ({op}) => op === 'remove';
143-
return partition(
144-
jsonFormattedDiff,
145-
isRemoveOp
146-
);
139+
export const partitionOps = (arr, fns) => {
140+
const initArr = Array(fns.length + 1).fill().map(() => []);
141+
return arr
142+
.map(item => {
143+
let position = fns.map(fn => fn(item)).indexOf(true);
144+
if (position < 0) {
145+
position = fns.length;
146+
}
147+
return { item, position };
148+
})
149+
.reduce((acc, item) => {
150+
acc[ item.position ].push(item.item);
151+
return acc;
152+
}, initArr);
147153
};
148-
149-
const reorderOps = jsonFormattedDiff => {
150-
const removeOpsOtherOps = partitionRemovedOps(jsonFormattedDiff);
151-
const removeOps = removeOpsOtherOps[0];
152-
const otherOps = removeOpsOtherOps[1];
153-
const removeOpsReverse = opsByDescendingOrder(removeOps);
154-
return removeOpsReverse.concat(otherOps);
154+
const isMoveOp = ({op}) => op === 'move';
155+
const isRemoveOp = ({op}) => op === 'remove';
156+
157+
const reorderOps = diff => {
158+
const [ moveOps, removedOps, restOps ] =
159+
partitionOps(diff, [ isMoveOp, isRemoveOp ]);
160+
const removeOpsReverse = opsByDescendingOrder(removedOps);
161+
return [ ...removeOpsReverse, ...moveOps, ...restOps ];
155162
};
156163

157164
let defaultInstance;

test/index.js

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,12 @@ describe('DiffPatcher', () => {
392392
path,
393393
});
394394

395+
const moveOp = (from, path) => ({
396+
op: 'move',
397+
from,
398+
path,
399+
});
400+
395401
const addOp = (path, value) => ({
396402
op: 'add',
397403
path,
@@ -477,10 +483,112 @@ describe('DiffPatcher', () => {
477483
const expectedDiff = [removeOp('/0'), removeOp('/0/items/0')];
478484
expectFormat(before, after, expectedDiff);
479485
});
486+
487+
it('should annotate move', () => {
488+
const before = [
489+
anObjectWithId('first'),
490+
anObjectWithId('second'),
491+
];
492+
const after = [
493+
anObjectWithId('second'),
494+
anObjectWithId('first'),
495+
];
496+
const expectedDiff = [moveOp('/1', '/0')];
497+
expectFormat(before, after, expectedDiff);
498+
});
499+
500+
it('should sort the ops', () => {
501+
expectFormat(
502+
{ 'hl': [ { id: 1, bla: 'bla' }, { id: 2, bla: 'ga' } ] },
503+
{ 'hl': [ { id: 2, bla: 'bla' }, { id: 1, bla: 'ga' } ] },
504+
[
505+
moveOp('/hl/1', '/hl/0'),
506+
replaceOp('/hl/0/bla', 'bla'),
507+
replaceOp('/hl/1/bla', 'ga'),
508+
]);
509+
});
480510
});
481511

482512
it('should annotate as moved op', () => {
483-
expectFormat([1, 2], [2, 1], [{ op: 'move', from: '/1', path: '/0' }]);
513+
expectFormat([1, 2], [2, 1], [moveOp('/1', '/0')]);
514+
});
515+
516+
it('should add full path for moved op', () => {
517+
expectFormat(
518+
{'hl': [1, 2]},
519+
{'hl': [2, 1]},
520+
[moveOp('/hl/1', '/hl/0')]);
521+
});
522+
523+
it('should put the full path in move op and sort by HL - #230', () => {
524+
const before = {
525+
'middleName': 'z',
526+
'referenceNumbers': [
527+
{
528+
'id': 'id-3',
529+
'referenceNumber': '123',
530+
'index': 'index-0',
531+
},
532+
{
533+
'id': 'id-1',
534+
'referenceNumber': '456',
535+
'index': 'index-1',
536+
},
537+
{
538+
'id': 'id-2',
539+
'referenceNumber': '789',
540+
'index': 'index-2',
541+
},
542+
],
543+
};
544+
const after = {
545+
'middleName': 'x',
546+
'referenceNumbers': [
547+
{
548+
'id': 'id-1',
549+
'referenceNumber': '456',
550+
'index': 'index-0',
551+
},
552+
{
553+
'id': 'id-3',
554+
'referenceNumber': '123',
555+
'index': 'index-1',
556+
},
557+
{
558+
'id': 'id-2',
559+
'referenceNumber': '789',
560+
'index': 'index-2',
561+
},
562+
],
563+
};
564+
const diff = [
565+
{
566+
'op': 'move',
567+
'from': '/referenceNumbers/1',
568+
'path': '/referenceNumbers/0',
569+
},
570+
{
571+
'op': 'replace',
572+
'path': '/middleName',
573+
'value': 'x',
574+
},
575+
{
576+
'op': 'replace',
577+
'path': '/referenceNumbers/0/index',
578+
'value': 'index-0',
579+
},
580+
{
581+
'op': 'replace',
582+
'path': '/referenceNumbers/1/index',
583+
'value': 'index-1',
584+
},
585+
];
586+
instance = new DiffPatcher({
587+
objectHash(obj) {
588+
return obj.id;
589+
},
590+
});
591+
expectFormat(before, after, diff);
484592
});
485593
});
486594

0 commit comments

Comments
 (0)