Skip to content

Commit

Permalink
Merge pull request #13 from streamich/flat
Browse files Browse the repository at this point in the history
Flat
  • Loading branch information
streamich authored Oct 11, 2020
2 parents 6805075 + 5ca0d86 commit 05af1c1
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 33 deletions.
15 changes: 12 additions & 3 deletions benchmarks/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ const {operationToOp} = require('../es6/json-patch');
const {applyPatch, applyOps} = require('../es6/json-patch/applyPatch/v1');
const {applyPatch: v2} = require('../es6/json-patch/applyPatch/v2');
const {applyPatch: v3} = require('../es6/json-patch/applyPatch/v3');
const {applyPatch: v4} = require('../es6/json-patch/applyPatch/v4');
const {applyPatch: applyPatchFastJsonPatch} = require('fast-json-patch');
const {deepClone} = require('../es6/json-patch/util');

const doc = { foo: { bar: 123 }, arr: [1, {}] };
const patch = [
Expand All @@ -28,15 +30,22 @@ suite
// .add(`json-joy (applyPatch v2)`, function() {
// v2(doc, patch, false);
// })
// .add(`json-joy (applyPatch v3)`, function() {
// v3(doc, patch, false);
// })
.add(`json-joy (applyPatch v3)`, function() {
v3(doc, patch, false);
})
.add(`json-joy (applyPatch v4)`, function() {
v4(doc, patch, false);
})
.add(`json-joy (applyOps)`, function() {
applyOps(doc, ops, false);
})
.add(`fast-json-patch`, function() {
applyPatchFastJsonPatch(doc, patch, false, false);
})
.add(`fast-json-patch (fast clone)`, function() {
const doc2 = deepClone(doc);
applyPatchFastJsonPatch(doc2, patch, false, true);
})
.on('cycle', function(event) {
console.log(String(event.target));
})
Expand Down
10 changes: 10 additions & 0 deletions benchmarks/no-clone.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const Benchmark = require('benchmark');
const {operationToOp} = require('../es6/json-patch');
const {applyPatch, applyOps} = require('../es6/json-patch/applyPatch/v1');
const {applyPatch: v3} = require('../es6/json-patch/applyPatch/v3');
const {applyPatch: v4} = require('../es6/json-patch/applyPatch/v4');
const {applyPatch: applyPatchFastJsonPatch} = require('fast-json-patch');

const patch = [
Expand All @@ -19,6 +21,14 @@ suite
const doc = { foo: { bar: 123 }, arr: [1, {}] };
applyPatch(doc, patch, true);
})
.add(`json-joy (applyPatch v3)`, function() {
const doc = { foo: { bar: 123 }, arr: [1, {}] };
v3(doc, patch, true);
})
.add(`json-joy (applyPatch v4)`, function() {
const doc = { foo: { bar: 123 }, arr: [1, {}] };
v4(doc, patch, true);
})
.add(`json-joy (applyOps)`, function() {
const doc = { foo: { bar: 123 }, arr: [1, {}] };
applyOps(doc, ops, true);
Expand Down
4 changes: 3 additions & 1 deletion src/json-patch/applyPatch/__tests__/applyPatch.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import { Operation } from '../../types';
import {applyPatch as v1} from '../v1';
import {applyPatch as v2} from '../v2';
import {applyPatch as v3} from '../v3';
import {applyPatch as v4} from '../v4';

const versions = {v1};
const versions = {v1, v4};
// const versions = {v3};

for (const name in versions) {
const applyPatch = (versions as any)[name];
Expand Down
52 changes: 52 additions & 0 deletions src/json-patch/applyPatch/__tests__/automated.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import tests_json from '../../__tests__/tests.json';
import spec_json from '../../__tests__/spec.json';
import { Operation } from '../../types';
import {applyPatch as v1} from '../v1';
import {applyPatch as v2} from '../v2';
import {applyPatch as v3} from '../v3';
import {applyPatch as v4} from '../v4';
import {validateOperation} from '../../validate';
import { deepClone } from '../../../../es6/json-patch/util';

const testSuites = [
{
name: 'tests.json',
tests: tests_json,
},
{
name: 'spec.json',
tests: spec_json,
},
];

const versions = {v1, v3, v4};
// const versions = {v3};

for (const name in versions) {
const applyPatch = (versions as any)[name];
describe(`applyPatch ${name}`, () => {
testSuites.forEach((s) => {
const suite = deepClone(s) as any;
describe(suite.name, () => {
suite.tests.forEach((test: any) => {
if (test.disabled) return;
const testName = test.comment || test.error || JSON.stringify(test.patch);
if (test.expected) {
it('should succeed: ' + testName, () => {
test.patch.forEach(validateOperation);
const {doc} = applyPatch(test.doc, test.patch, true);
expect(doc).toEqual(test.expected);
});
} else if (test.error || test.patch[0].op === 'test') {
it('should throw an error: ' + testName, () => {
expect(() => {
test.patch.forEach(validateOperation);
applyPatch(test.doc, test.patch, true);
}).toThrow();
});
} else throw new Error('invalid test case');
});
});
});
});
}
2 changes: 1 addition & 1 deletion src/json-patch/applyPatch/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from './v1';
export * from './v4';
104 changes: 78 additions & 26 deletions src/json-patch/applyPatch/v3.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
/* tslint:disable no-string-throw */

import {deepClone} from '../util';
import {Operation} from '../types';
import {findByPointer, hasOwnProperty, isArrayReference, isObjectReference, isValidIndex, unescapeComponent} from '../../json-pointer';
import {findByPointer, hasOwnProperty, unescapeComponent} from '../../json-pointer';
const isEqual = require('fast-deep-equal');

export interface OpResult {
doc: unknown;
Expand All @@ -16,24 +15,31 @@ export interface PatchResult {

const {isArray} = Array;

export function applyPatch(doc: unknown, patch: readonly Operation[], mutate: boolean): PatchResult {
if (!mutate) doc = deepClone(doc);
const res: OpResult[] = [];
for (let i = 0; i < patch.length; i++) {
const operation = patch[i];
export function applyOperation(doc: unknown, operation: Operation): OpResult {
const path = operation.path as string;
const isRoot = !path;
if (isRoot) {
switch (operation.op) {
case 'add':
case 'replace':
doc = operation.value;
break;
return {doc: operation.value, old: doc};
case 'remove':
doc = null;
break;
return {doc: null, old: doc};
case 'move': {
const {val} = findByPointer(operation.from, doc);
return {doc: val, old: doc};
}
case 'copy': {
const {val} = findByPointer(operation.from, doc);
return {doc: val, old: doc};
}
case 'test': {
if (!isEqual(operation.value, doc)) throw new Error('TEST');
return {doc};
}
}
break;
return {doc};
};
let indexOfSlash: number = 0;
let indexAfterSlash: number = 1;
Expand All @@ -45,31 +51,48 @@ export function applyPatch(doc: unknown, patch: readonly Operation[], mutate: bo
? path.substring(indexAfterSlash, indexOfSlash)
: path.substring(indexAfterSlash);
indexAfterSlash = indexOfSlash + 1;
if (indexOfSlash === -1) break;
if (isArray(obj)) {
if (key === '-') key = obj.length;
const length = obj.length;
if (key === '-') key = length;
else {
key = ~~key;
if (key < 0) throw 'INVALID_INDEX';
const key2 = ~~key;
if (('' + (key2)) !== key) throw new Error('INVALID_INDEX');
key = key2;
if (key < 0 || key > length) throw new Error('INVALID_INDEX');
}
if (indexOfSlash === -1) {
switch (operation.op) {
case 'add': {
const old = obj[key];
if (key < obj.length) obj.splice(key, 0, operation.value);
else obj.push(operation.value);
break;
return {doc, old};
}
case 'replace': {
const old = obj[key];
obj[key] = operation.value;
break;
return {doc, old};
}
case 'remove': {
const old = obj[key];
obj.splice(key as any, 1);
break;
return {doc, old};
}
case 'move': {
obj.splice(key as any, 1);
break;
const removeResult = applyOperation(doc, {op: 'remove', path: operation.from});
return applyOperation(removeResult.doc, {op: 'add', path: operation.path, value: removeResult.old!});
}
case 'copy': {
const old = obj[key];
const {val} = findByPointer(operation.from, doc);
const value = deepClone(val);
if (key < obj.length) obj.splice(key, 0, value);
else obj.push(value);
return {doc, old};
}
case 'test': {
if (!isEqual(operation.value, obj[key])) throw new Error('TEST');
return {doc};
}
}
break;
Expand All @@ -80,24 +103,53 @@ export function applyPatch(doc: unknown, patch: readonly Operation[], mutate: bo
if (indexOfSlash === -1) {
switch (operation.op) {
case 'add': {
const old = (obj as any)[key];
(obj as any)[key] = operation.value;
break;
return {doc, old};
}
case 'replace': {
const old = (obj as any)[key];
(obj as any)[key] = operation.value;
break;
return {doc, old};
}
case 'remove': {
const old = (obj as any)[key];
delete (obj as any)[key];
break;
return {doc, old};
}
case 'move': {
const removeResult = applyOperation(doc, {op: 'remove', path: operation.from});
const addResult = applyOperation(doc, {op: 'add', path: operation.path, value: removeResult.old!});
return addResult;
}
case 'copy': {
const {val} = findByPointer(operation.from, doc);
const value = deepClone(val);
const old = (obj as any)[key];
(obj as any)[key] = value;
return {doc, old};
}
case 'test': {
if (!isEqual(operation.value, (obj as any)[key])) throw new Error('TEST');
return {doc};
}
}
break;
}
obj = hasOwnProperty(obj, key) ? (obj as any)[key] : undefined;
// tslint:disable-next-line
} else throw 'NOT_FOUND';
} else throw new Error('NOT_FOUND');
}
return {doc};
}

export function applyPatch(doc: unknown, patch: readonly Operation[], mutate: boolean): PatchResult {
if (!mutate) doc = deepClone(doc);
const res: OpResult[] = [];
for (let i = 0; i < patch.length; i++) {
const operation = patch[i];
const opResult = applyOperation(doc, operation);
res.push(opResult);
doc = opResult.doc;
}
return {doc, res};
}
43 changes: 43 additions & 0 deletions src/json-patch/applyPatch/v4.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {deepClone} from '../util';
import {Operation} from '../types';
import {Op, operationToOp} from '../op';

export interface OpResult {
doc: unknown;
old?: unknown;
}

export interface PatchResult {
doc: unknown;
res: readonly OpResult[];
}

export function applyOp(doc: unknown, op: Op, mutate: boolean): OpResult {
if (!mutate) doc = deepClone(doc);
return op.apply(doc);
}

export function applyOps(doc: unknown, ops: readonly Op[], mutate: boolean): PatchResult {
if (!mutate) doc = deepClone(doc);
const res: OpResult[] = [];
const length = ops.length;
for (let i = 0; i < length; i++) {
const opResult = ops[i].apply(doc);
doc = opResult.doc;
res.push(opResult);
}
return {doc, res};
}

export function applyPatch(doc: unknown, patch: readonly Operation[], mutate: boolean): PatchResult {
if (!mutate) doc = deepClone(doc);
const res: OpResult[] = [];
const length = patch.length;
for (let i = 0; i < length; i++) {
const op = operationToOp(patch[i]);
const opResult = op.apply(doc);
doc = opResult.doc;
res.push(opResult);
}
return {doc, res};
}
7 changes: 5 additions & 2 deletions src/json-pointer/findByPointer/v5.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ export const findByPointer = (pointer: string, val: unknown): Reference => {
indexAfterSlash = indexOfSlash + 1;
obj = val;
if (isArray(obj)) {
if (key === '-') key = obj.length;
const length = obj.length;
if (key === '-') key = length;
else {
key = ~~key;
const key2 = ~~key;
if (('' + (key2)) !== key) throw new Error('INVALID_INDEX');
key = key2;
if (key < 0) throw 'INVALID_INDEX';
}
val = obj[key];
Expand Down

0 comments on commit 05af1c1

Please sign in to comment.