Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to generate test operations #226

Closed
wants to merge 12 commits into from
42 changes: 36 additions & 6 deletions README.md
Expand Up @@ -154,6 +154,24 @@ var patch = jsonpatch.generate(observer);
// ];
```

Generating patches with test operations:

```js
var document = { firstName: "Joachim", lastName: "Wester", contactDetails: { phoneNumbers: [ { number:"555-123" }] } };
var observer = jsonpatch.observe(document, undefined, true);
document.firstName = "Albert";
document.contactDetails.phoneNumbers[0].number = "123";
document.contactDetails.phoneNumbers.push({ number:"456" });
var patch = jsonpatch.generate(observer);
// patch == [
// { op: "test", path: "/firstName", value: "Joachim"},
// { op: "replace", path: "/firstName", value: "Albert"},
// { op: "test", path: "/contactDetails/phoneNumbers/0/number", value: "555-123" },
// { op: "replace", path: "/contactDetails/phoneNumbers/0/number", value: "123" },
// { op: "add", path: "/contactDetails/phoneNumbers/1", value: {number:"456"}}
// ];
```

Comparing two object trees:

```js
Expand All @@ -163,6 +181,18 @@ var diff = jsonpatch.compare(documentA, documentB);
//diff == [{op: "replace", path: "/user/lastName", value: "Collins"}]
```

Comparing two object trees with test operations:

```js
var documentA = {user: {firstName: "Albert", lastName: "Einstein"}};
var documentB = {user: {firstName: "Albert", lastName: "Collins"}};
var diff = jsonpatch.compare(documentA, documentB, {inversible: true});
//diff == [
// {op: "test", path: "/user/lastName", value: "Einstein"},
// {op: "replace", path: "/user/lastName", value: "Collins"}
// ];
```

Validating a sequence of patches:

```js
Expand Down Expand Up @@ -262,17 +292,17 @@ Retrieves a value from a JSON document by a JSON pointer.

Returns the value.

#### `jsonpatch.observe(document: any, callback?: Function): Observer`
#### `jsonpatch.observe(document: any, callback?: Function, inversible: boolean = false): Observer`

Sets up an deep observer on `document` that listens for changes in object tree. When changes are detected, the optional
callback is called with the generated patches array as the parameter.
callback is called with the generated patches array as the parameter. If inversible is true, then observer will generate test operations.

Returns `observer`.

#### `jsonpatch.generate(document: any, observer: Observer): Operation[]`
#### `jsonpatch.generate(document: any, observer: Observer, opts?: { inversible: boolean }): Operation[]`

If there are pending changes in `obj`, returns them synchronously. If a `callback` was defined in `observe`
method, it will be triggered synchronously as well.
method, it will be triggered synchronously as well. If opts.inversible is undefined then fallback to observer.inversible.

If there are no pending changes in `obj`, returns an empty array (length 0).

Expand All @@ -282,9 +312,9 @@ Destroys the observer set up on `document`.

Any remaining changes are delivered synchronously (as in `jsonpatch.generate`). Note: this is different that ES6/7 `Object.unobserve`, which delivers remaining changes asynchronously.

#### `jsonpatch.compare(document1: any, document2: any): Operation[]`
#### `jsonpatch.compare(document1: any, document2: any, opts?: { inversible: boolean }): Operation[]`

Compares object trees `document1` and `document2` and returns the difference relative to `document1` as a patches array.
Compares object trees `document1` and `document2` and returns the difference relative to `document1` as a patches array. If opts.inversible is true, test operations will be generated.

If there are no differences, returns an empty array (length 0).

Expand Down
29 changes: 20 additions & 9 deletions dist/fast-json-patch.js
Expand Up @@ -646,7 +646,7 @@ exports.validate = validate;
*/
var helpers_1 = __webpack_require__(0);
var core_1 = __webpack_require__(1);
/* export all core functions */
/* export all core functions and types */
var core_2 = __webpack_require__(1);
exports.applyOperation = core_2.applyOperation;
exports.applyPatch = core_2.applyPatch;
Expand Down Expand Up @@ -694,7 +694,8 @@ exports.unobserve = unobserve;
/**
* Observes changes made to an object, which can then be retrieved using generate
*/
function observe(obj, callback) {
function observe(obj, callback, inversible) {
if (inversible === void 0) { inversible = false; }
var patches = [];
var observer;
var mirror = getMirror(obj);
Expand All @@ -709,7 +710,7 @@ function observe(obj, callback) {
if (observer) {
return observer;
}
observer = {};
observer = { inversible: inversible };
mirror.value = helpers_1._deepClone(obj);
if (callback) {
observer.callback = callback;
Expand Down Expand Up @@ -766,9 +767,11 @@ exports.observe = observe;
/**
* Generate an array of patches from an observer
*/
function generate(observer) {
function generate(observer, opts) {
if (opts === void 0) { opts = {}; }
var mirror = beforeDict.get(observer.object);
_generate(mirror.value, observer.object, observer.patches, "");
var inversible = typeof opts.inversible !== "undefined" ? opts.inversible : observer.inversible;
_generate(mirror.value, observer.object, observer.patches, "", { inversible: inversible });
if (observer.patches.length) {
core_1.applyPatch(mirror.value, observer.patches);
}
Expand All @@ -783,7 +786,8 @@ function generate(observer) {
}
exports.generate = generate;
// Dirty check if obj is different from mirror, generate patches and update mirror
function _generate(mirror, obj, patches, path) {
function _generate(mirror, obj, patches, path, opts) {
if (opts === void 0) { opts = { inversible: false }; }
if (obj === mirror) {
return;
}
Expand All @@ -794,27 +798,34 @@ function _generate(mirror, obj, patches, path) {
var oldKeys = helpers_1._objectKeys(mirror);
var changed = false;
var deleted = false;
var inversible = opts.inversible;
//if ever "move" operation is implemented here, make sure this test runs OK: "should not generate the same patch twice (move)"
for (var t = oldKeys.length - 1; t >= 0; t--) {
var key = oldKeys[t];
var oldVal = mirror[key];
if (helpers_1.hasOwnProperty(obj, key) && !(obj[key] === undefined && oldVal !== undefined && Array.isArray(obj) === false)) {
var newVal = obj[key];
if (typeof oldVal == "object" && oldVal != null && typeof newVal == "object" && newVal != null) {
_generate(oldVal, newVal, patches, path + "/" + helpers_1.escapePathComponent(key));
_generate(oldVal, newVal, patches, path + "/" + helpers_1.escapePathComponent(key), opts);
}
else {
if (oldVal !== newVal) {
changed = true;
if (inversible)
patches.push({ op: "test", path: path + "/" + helpers_1.escapePathComponent(key), value: helpers_1._deepClone(oldVal) });
patches.push({ op: "replace", path: path + "/" + helpers_1.escapePathComponent(key), value: helpers_1._deepClone(newVal) });
}
}
}
else if (Array.isArray(mirror) === Array.isArray(obj)) {
if (inversible)
patches.push({ op: "test", path: path + "/" + helpers_1.escapePathComponent(key), value: helpers_1._deepClone(oldVal) });
patches.push({ op: "remove", path: path + "/" + helpers_1.escapePathComponent(key) });
deleted = true; // property has been deleted
}
else {
if (inversible)
patches.push({ op: "test", path: path, value: mirror });
patches.push({ op: "replace", path: path, value: obj });
changed = true;
}
Expand All @@ -832,9 +843,9 @@ function _generate(mirror, obj, patches, path) {
/**
* Create an array of patches from the differences in two objects
*/
function compare(tree1, tree2) {
function compare(tree1, tree2, opts) {
var patches = [];
_generate(tree1, tree2, patches, '');
_generate(tree1, tree2, patches, '', opts);
return patches;
}
exports.compare = compare;
Expand Down