Giter VIP home page Giter VIP logo

json-patch's Introduction

JSON-Patch

A leaner and meaner implementation of JSON-Patch. Small footprint. High performance.

Build Status

With JSON-Patch, you can:

  • apply patches (arrays) and single operations on JS object
  • validate a sequence of patches
  • observe for changes and generate patches when a change is detected
  • compare two objects to obtain the difference

Tested in Firefox, Chrome, Edge, Safari, IE11, Deno and Node.js

Why you should use JSON-Patch

JSON-Patch (RFC6902) is a standard format that allows you to update a JSON document by sending the changes rather than the whole document. JSON Patch plays well with the HTTP PATCH verb (method) and REST style programming.

Mark Nottingham has a nice blog about it.

Install

Download as ZIP or install the current version using a package manager (and save it as a dependency):

# NPM
npm install fast-json-patch --save

Adding to your project

In a web browser

Load the bundled distribution script:

<script src="dist/fast-json-patch.min.js"></script>

In browsers that support ECMAScript modules, the below code uses this library as a module:

<script type="module">
  import * as jsonpatch from 'fast-json-patch/index.mjs';
  import { applyOperation } from 'fast-json-patch/index.mjs';
</script>

In Node.js

In Node 12+ with --experimental-modules flag, the below code uses this library as an ECMAScript module:

import * as jsonpatch from 'fast-json-patch/index.mjs';
import { applyOperation } from 'fast-json-patch/index.mjs';

In Webpack (and most surely other bundlers based on Babel), the below code uses this library as an ECMAScript module:

import * as jsonpatch from 'fast-json-patch';
import { applyOperation } from 'fast-json-patch';

In standard Node, the below code uses this library as a CommonJS module:

const { applyOperation } = require('fast-json-patch');
const applyOperation = require('fast-json-patch').applyOperation;

Directories

Directories used in this package:

  • dist/ - contains ES5 files for a Web browser
  • commonjs/ - contains CommonJS module and typings
  • module/ - contains ECMAScript module and typings
  • src/ - contains TypeScript source files

API

function applyPatch<T>(document: T, patch: Operation[], validateOperation?: boolean | Validator<T>, mutateDocument: boolean = true, banPrototypeModifications: boolean = true): PatchResult<T>

Applies patch array on obj.

  • document The document to patch
  • patch a JSON-Patch array of operations to apply
  • validateOperation Boolean for whether to validate each operation with our default validator, or to pass a validator callback
  • mutateDocument Whether to mutate the original document or clone it before applying
  • banPrototypeModifications Whether to ban modifications to __proto__, defaults to true.

An invalid patch results in throwing an error (see jsonpatch.validate for more information about the error object).

It modifies the document object and patch - it gets the values by reference. If you would like to avoid touching your patch array values, clone them: jsonpatch.applyPatch(document, jsonpatch.deepClone(patch)).

Returns an array of OperationResult objects - one item for each item in patches, each item is an object {newDocument: any, test?: boolean, removed?: any}.

  • test - boolean result of the test
  • remove, replace and move - original object that has been removed
  • add (only when adding to an array) - index at which item has been inserted (useful when using - alias)
  • ** Note: It throws TEST_OPERATION_FAILED error if test operation fails. **

  • ** Note II: the returned array has newDocument property that you can use as the final state of the patched document **.

  • ** Note III: By default, when banPrototypeModifications is true, this method throws a TypeError when you attempt to modify an object's prototype.

  • See Validation notes.

Example:

var document = { firstName: "Albert", contactDetails: { phoneNumbers: [] } };
var patch = [
  { op: "replace", path: "/firstName", value: "Joachim" },
  { op: "add", path: "/lastName", value: "Wester" },
  { op: "add", path: "/contactDetails/phoneNumbers/0", value: { number: "555-123" }  }
];
document = jsonpatch.applyPatch(document, patch).newDocument;
// document == { firstName: "Joachim", lastName: "Wester", contactDetails: { phoneNumbers: [{number:"555-123"}] } };

function applyOperation<T>(document: T, operation: Operation, validateOperation: boolean | Validator<T> = false, mutateDocument: boolean = true, banPrototypeModifications: boolean = true, index: number = 0): OperationResult<T>

Applies single operation object operation on document.

  • document The document to patch
  • operation The operation to apply
  • validateOperation Whether to validate the operation, or to pass a validator callback
  • mutateDocument Whether to mutate the original document or clone it before applying
  • banPrototypeModifications Whether to ban modifications to __proto__, defaults to true.
  • index The index of the operation in your patch array. Useful for better error reporting when that operation fails to apply.

It modifies the document object and operation - it gets the values by reference. If you would like to avoid touching your values, clone them: jsonpatch.applyOperation(document, jsonpatch.deepClone(operation)).

Returns an OperationResult object {newDocument: any, test?: boolean, removed?: any}.

  • ** Note: It throws TEST_OPERATION_FAILED error if test operation fails. **

  • ** Note II: By default, when banPrototypeModifications is true, this method throws a TypeError when you attempt to modify an object's prototype.

  • See Validation notes.

Example:

var document = { firstName: "Albert", contactDetails: { phoneNumbers: [] } };
var operation = { op: "replace", path: "/firstName", value: "Joachim" };
document = jsonpatch.applyOperation(document, operation).newDocument;
// document == { firstName: "Joachim", contactDetails: { phoneNumbers: [] }}

jsonpatch.applyReducer<T>(document: T, operation: Operation, index: number): T

Ideal for patch.reduce(jsonpatch.applyReducer, document).

Applies single operation object operation on document.

Returns the a modified document.

Note: It throws TEST_OPERATION_FAILED error if test operation fails.

Example:

var document = { firstName: "Albert", contactDetails: { phoneNumbers: [ ] } };
var patch = [
  { op:"replace", path: "/firstName", value: "Joachim" },
  { op:"add", path: "/lastName", value: "Wester" },
  { op:"add", path: "/contactDetails/phoneNumbers/0", value: { number: "555-123" } }
];
var updatedDocument = patch.reduce(applyReducer, document);
// updatedDocument == { firstName:"Joachim", lastName:"Wester", contactDetails:{ phoneNumbers[ {number:"555-123"} ] } };

jsonpatch.deepClone(value: any): any

Returns deeply cloned value.

jsonpatch.escapePathComponent(path: string): string

Returns the escaped path.

jsonpatch.unescapePathComponent(path: string): string

Returns the unescaped path.

jsonpatch.getValueByPointer(document: object, pointer: string)

Retrieves a value from a JSON document by a JSON pointer.

Returns the value.

jsonpatch.observe(document: any, callback?: Function): 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.

Returns observer.

jsonpatch.generate(document: any, observer: Observer, invertible = false): 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. If invertible is true, then each change will be preceded by a test operation of the value before the change.

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

Example:

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

Example of generating patches with test operations for values in the first object:

var document = { firstName: "Joachim", lastName: "Wester", contactDetails: { phoneNumbers: [ { number:"555-123" }] } };
var observer = jsonpatch.observe(document);
document.firstName = "Albert";
document.contactDetails.phoneNumbers[0].number = "123";
document.contactDetails.phoneNumbers.push({ number:"456" });
var patch = jsonpatch.generate(observer, true);
// 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"}}
// ];

jsonpatch.unobserve(document, observer)

jsonpatch.unobserve(document: any, observer: Observer): void

type JsonableObj = { [key:string]: Jsonable };
type JsonableArr = Jsonable[];
type Jsonable = JsonableArr | JsonableObj | string | number | boolean | null;

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, document2, invertible)

jsonpatch.compare(document1: Jsonable, document2: Jsonable, invertible = false): Operation[]

type JsonableObj = { [key:string]: Jsonable };
type JsonableArr = Jsonable[];
type Jsonable = JsonableArr | JsonableObj | string | number | boolean | null;

Compares object trees document1 and document2 and returns the difference relative to document1 as a patches array. If invertible is true, then each change will be preceded by a test operation of the value in document1.

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

Example:

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

Example of comparing two object trees with test operations for values in the first object:

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

jsonpatch.validate(patch: Operation[], document?: any, validator?: Function): JsonPatchError

See Validation notes

Validates a sequence of operations. If document parameter is provided, the sequence is additionally validated against the object tree.

If there are no errors, returns undefined. If there is an errors, returns a JsonPatchError object with the following properties:

  • name String - short error code
  • message String - long human readable error message
  • index Number - index of the operation in the sequence
  • operation Object - reference to the operation
  • tree Object - reference to the tree

Possible errors:

Error name Error message
SEQUENCE_NOT_AN_ARRAY Patch sequence must be an array
OPERATION_NOT_AN_OBJECT Operation is not an object
OPERATION_OP_INVALID Operation op property is not one of operations defined in RFC-6902
OPERATION_PATH_INVALID Operation path property is not a valid string
OPERATION_FROM_REQUIRED Operation from property is not present (applicable in move and copy operations)
OPERATION_VALUE_REQUIRED Operation value property is not present, or undefined (applicable in add, replace and test operations)
OPERATION_VALUE_CANNOT_CONTAIN_UNDEFINED Operation value property object has at least one undefined value (applicable in add, replace and test operations)
OPERATION_PATH_CANNOT_ADD Cannot perform an add operation at the desired path
OPERATION_PATH_UNRESOLVABLE Cannot perform the operation at a path that does not exist
OPERATION_FROM_UNRESOLVABLE Cannot perform the operation from a path that does not exist
OPERATION_PATH_ILLEGAL_ARRAY_INDEX Expected an unsigned base-10 integer value, making the new referenced value the array element with the zero-based index
OPERATION_VALUE_OUT_OF_BOUNDS The specified index MUST NOT be greater than the number of elements in the array
TEST_OPERATION_FAILED When operation is test and the test fails, applies to applyReducer.

Example:

var obj = {user: {firstName: "Albert"}};
var patches = [{op: "replace", path: "/user/firstName", value: "Albert"}, {op: "replace", path: "/user/lastName", value: "Einstein"}];
var errors = jsonpatch.validate(patches, obj);
if (errors.length == 0) {
 //there are no errors!
}
else {
  for (var i=0; i < errors.length; i++) {
    if (!errors[i]) {
      console.log("Valid patch at index", i, patches[i]);
    }
    else {
      console.error("Invalid patch at index", i, errors[i], patches[i]);
    }
  }
}

OperationResult Type

Functions applyPatch and applyOperation both return OperationResult object. This object is:

{newDocument: any, test?: boolean, removed?: any}

Where:

  • newDocument: the new state of the document after the patch/operation is applied.
  • test: if the operation was a test operation. This will be its result.
  • removed: contains the removed, moved, or replaced values from the document after a remove, move or replace operation.

Validation Notes

Functions applyPatch, applyOperation, and validate accept a validate/ validator parameter:

  • If the validateOperation parameter is set to false, validation will not occur.
  • If set to true, the patch is extensively validated before applying using jsonpatch's default validation.
  • If set to a function callback, the patch is validated using that function.

If you pass a validator, it will be called with four parameters for each operation, function(operation, index, tree, existingPath) and it is expected to throw JsonPatchError when your conditions are not met.

  • operation The operation it self.
  • index operation's index in the patch array (if application).
  • tree The object that is supposed to be patched.
  • existingPath the path operation points to.

Overwriting and move Operation

When the target of the move operation already exists, it is cached, deep cloned and returned as removed in OperationResult.

undefineds (JS to JSON projection)

As undefined type does not exist in JSON, it's also not a valid value of JSON Patch operation. Therefore jsonpatch will not generate JSON Patches that sets anything to undefined.

Whenever a value is set to undefined in JS, JSON-Patch methods generate and compare will treat it similarly to how JavaScript method JSON.stringify (MDN) treats them:

If undefined (...) is encountered during conversion it is either omitted (when it is found in an object) or censored to null (when it is found in an array).

See the ECMAScript spec for details.

Specs/tests

Changelog

To see the list of recent changes, see Releases.

Footprint

4 KB minified and gzipped (12 KB minified)

Performance

image

image

Tested on 29.08.2018. Compared libraries:

We aim the tests to be fair. Our library puts performance as the #1 priority, while other libraries can have different priorities. If you'd like to update the benchmarks or add a library, please fork the perf.zone benchmarks linked above and open an issue to include new results.

License

MIT

json-patch's People

Contributors

aloscha avatar alromh87 avatar alshakero avatar amccausl avatar andwah avatar baranga avatar bendiy avatar bruce-one avatar danez avatar dependabot[bot] avatar felixfbecker avatar gsferreira avatar ianvonholt avatar jamesplease avatar jamieslome avatar jla avatar johanneszorn avatar joozek78 avatar joshua-mcginnis avatar kamranasif avatar kumavis avatar psmolenski avatar pytlesk4 avatar sgoll avatar shackbarth avatar sonnyp avatar starcounter-jack avatar thegr8nik avatar tomalec avatar warpech avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

json-patch's Issues

Remove sourceMappingURL Comment

I'm compiling all of my JS files into a single file with grunt, and the following line is forcing the browser to attempt to download a file that does not exist:
//# sourceMappingURL=json-patch.min.js.map

Manually specifying this in the file is not required, any browser that supports map files will automatically look for them in the same directory. It's only required if you want to put the map files in a different directory.

{"op":"add"} has no value if the value is undefined.

RFC 6902, section 4.1 ("add") says: "The operation object MUST contain a "value" member whose content specifies the value to be added."

jsonpatch.compare() can, however, produce an add operation with no value when the value is undefined.

Test case:

original = {foo: 1};
changed = {foo: 1, bar: undefined};
patch = jsonpatch.compare(original, changed);
jsonpatch.apply(original, patch);

Result: Error: 'value' MUST be defined

Using a Object.observe polyfill

Hi,

Isn't a different concern for this project to have its own Object.observer? Wouldn't be better if it uses a external module to do it or spin off the current one?

https://github.com/jdarling/Object.observe

Even further, is is really necessary to observe a object to create a diff? Wouldn't be the responsibility of the ones using this module to know when and where to make a diff? So, is the concern of json-path to do that?

ps: json-patch is a great project! tks!

validate corrupts patches when adding array then appending

Hello,

You can try the following code:

var jsonpatch = require('./src/json-patch');
var patches = [{op: 'add', value: [], path: '/foo'}, {op: 'add', value: 2, path: '/foo/-'}];
jsonpatch.validate(patches, {});
console.log(patches);

It outputs:

[ { op: 'add', value: [ 2 ], path: '/foo' },
  { op: 'add', value: 2, path: '/foo/-' } ]

As you can see the value object of the first patch has been modified. Instead of an empty array it is now an array containing the value of the second patch.

can I use JSON-Patch for array this way?

assume that there's an object like:

var obj_src = {
  name: "jone",
  city: "Sydney",
  pets: [
    {nickname: "volo", category: "dog", age: "3"},
    {nickname: "wabit", category: "cat", age: "5"},
    {nickname: "moaoaa", category: "tiger", age: "1"},
    {nickname: "gofast", category: "lion", age: "3"}
  ]
}

if pets' nickname is unique, can I use patch like {op: "replace", path: "/pets[nickname=wabit]/age", value: "9"} to alter wabit's age instead using {op: "replace", path: "/pets/1/age", value: "9"} because sometimes I don't know the array order.

The compare function creates non-deep patch

In this test patch has no the 'contactDetails' field:

var collection1 = {},
    collection2 = {},
    iteration = 100;

var json1 = { firstName:"Joachim", lastName:"Wester", num:123, contactDetails: { phoneNumbers: [ { number:"555-123" }] } },
    json2 = { firstName:"Ivan", lastName:"test", key: [], contactDetails: { phoneNumbers: [ { number:"555-123" }, { number:"666-123" }] } };

//fill array
var t0 = performance.now();
for(var i=0; i < iteration; i++) {
    collection1['key'+i] = _.clone(json1); //underscore.js
    collection2['key'+i] = _.clone(json2); //underscore.js
}
var t1 = performance.now();
console.log('Test 0. Took ' + (t1 - t0) + ' ms');
console.log(collection1);
console.log(collection2);

//get patch
var t0 = performance.now();
for(var i in collection1) {
    var patch = jsonpatch.compare(collection1[i], collection2[i]);
}
var t1 = performance.now();
console.log('Test 1. Took ' + (t1 - t0) + ' ms');
console.log(patch);

//// output
[Object, Object, Object, Object]
0: Object
op: "remove"
path: "/num"
proto: Object
1: Object
op: "replace"
path: "/lastName"
value: "test"
proto: Object
2: Object
op: "replace"
path: "/firstName"
value: "Ivan"
proto: Object
3: Object
op: "add"
path: "/key"
value: Array[0]
proto: Object
length: 4
proto: Array[0]

should generate nice remove op on array.splice

Here's the test that fails:

it('should generate nice remove op on array.splice', function() {
          obj = { items: ["a", "b", "c"]};
          var observer = jsonpatch.observe(obj);

          obj.items.splice(1, 1); // <<-- removing second item in-place

          patches = jsonpatch.generate(observer);

          // Expect to have only one 'remove' operation, otherwise patches for big arrays are way too long:
          // essentially it says to replace *every* item and remove last one.
          // In case array items are not trivial, this may cause performance issues too, I guess.
          expect(patches).toEqual([
              { op: 'remove', path: '/items/1' }
          ]);

          obj2 = { items: ["a", "b", "c"]};
          jsonpatch.apply(obj2,patches);
          expect(obj).toEqualInJson(obj2);
      });

Now it fails as following:

duplex generate should generate nice remove on array.splice.
Expected [ { op : 'remove', path : '/items/2' }, { op : 'replace', path : '/items/1', value : 'c' } ] to equal [ { op : 'remove', path : '/items/1' } ].

reading value at path using jsonpointer

I am looking for a way, without introducing a new library, to read value at given path using json-pointer notation. Something like jsonpatch.get(object, "/pointer/to/key") to return value at that path.

I know it's really neither patching's responsibility, nor it's specified anywhere in RFC but if we have such mechanism(method), it can be very useful.

jsonpatch.generate modifies observed object

Hi,

if I call jsonpatch.generate() the first time it generates the patches normally, but modifies the observed object, so the subsequent calls to jsonpatch.generate() don't generate any patches. Is it intentional, or is it a bug?

Regards
Gábor

Undefined properties cause an exception to be thrown

Consider the following code:

var object = {
  test: undefined
};

var observer = jsonpatch.observe(object);
var result = jsonpatch.generate(observer);

JSON-Patch throws a SyntaxError exception when trying to generate the patch at line 421 of json-patch-duplex.js (in the function _generate):

mirror[key] = JSON.parse(JSON.stringify(obj[key]));

obj[key] is undefined, causing JSON.stringify() to return undefined, which JSON.parse() cannot handle ("Unexpected token u").

The expected behavior is that no patches should be generated, as nothing has changed.

Replace operation cannot be applied to a root path

The following code does not work correctly:

var obj = {"hello": "world"};
jsonpatch.apply(obj, [{"op":"replace","path":"/","value":{"hello": "universe"}}]);

Expected result obj:

{"hello": "universe"}

Actual result obj:

{"hello": "world", "": {"hello": "universe"}}

jsonpatch.compare should not modify the mirror object

I totally understand that jsonpatch.generate modifies the objects it watches to always only detect most recent changes (as discussed in #18 ) of an object. In my opinion this should not be the case for jsonpatch.compare as I explicitly use this method with objects that are not under control of jsonpatch observers. Thus modification of these objects should be under my control as well.

feature request: patch validator

Thanks for this project.

A nice feature would be to validate that a patch document is a valid patch doc. This would allow users to pre-validate that the client sent a valid patch document before the server uses it.

Something like:
jsonpatch.isValid(patch); //true = valid, false = invalid

Or you could return errors, but that might be difficult. Ultimately, if a client sends a bad patch, we're just going to return an http bad request.

Is there a chance to obtain a diff between obj1 and obj2?

My application need to get a diff between two different objects, obj1 and obj2 !
Is there a syntax to use it like that?
I don't have an "original object" that is modified at run-time, in order to "observe" it!
I load obj1 from a database and build obj2 from a form, and need to have the diff!
Thanks,
Teo

Wrong updating document when removing several array elements

I don't know does it issue related to fast-json-path directly but hope you could give some advices. Let's say we have the following document for patching:

{ "foo": [ "bar", "baz", "foo", "fox" ] }

And trying to remove "bar" and "baz" elements in single operations batch:

[
    { "op": "remove", "path": "/foo/0" },
    { "op": "remove", "path": "/foo/1" }
]

I'm expecting to get the following result:

    { "foo": ["foo", "fox" ] }

But rather then I get the following:

 { "foo": [ "baz", "fox" ] }

I understand that after applying first operation { "op": "remove", "path": "/foo/0" } array indexes are get recalculates and the following operation { "op": "remove", "path": "/foo/1" } applies to updated array.

Let me give some advises how could I workaround this?

Address field shows null

In response from the server, To and From address come as null:

"To":{"Address$":null}

Even if I update it with a new value (patch request), server still restores the null value.

Compare to cujojs/jiff

In your readme you compare to a two out of the three other json-patch libraries mentioned on jsonpatch.com. Any chance you could add the third one to the comparison?

TypeError exception thrown when 'path' is not defined after calling jsonpatch.validate

A TypeError exception is being thrown when the path parameter isn't specified (or misspelled) in a patch. This is handled correctly when the op and value parameters aren't specified, but not path.

TypeError: Cannot read property 'split' of undefined
at Object.apply (node_modules/fast-json-patch/src/json-patch-duplex.js:548:34)
at Object.validate (node_modules/fast-json-patch/src/json-patch-duplex.js:686:23)

For example:

[
  {
    "op"    : "replace",
    "value" : "Not working"
  }
]

Replacing null returns error

@Starcounter-Jack I'm on 0.5.1.

My object: {foo: null}
My patch: [ { op: 'replace', path: '/foo', value: 'whatever' } ]
Returns: "Cannot perform the operation at a path that does not exist".
Expected: {foo: 'whatever'}

Support multiple callbacks

var x = {};

jsonpatch.observe(x, function() {
  console.log('One');
});
jsonpatch.observe(x, function() {
  console.log('Two');
});

x.someValue = true;

It should execute both functions.

Date comparison always yields a patch

Originally described here

When you do a comparison on an object that includes dates, all dates end up resulting in patches. This is because JSON.parse(JSON.stringify)) is used to generate an internal cache object, but that does not result in an object with identical values. Dates get converted to strings:

d = new Date();
foo = {x: 10, y: "test", z: d};
jsonpatch.observe(foo);
bar = jsonpatch.observe(foo);
jsonpatch.generate(bar);

Strange behaviour! If path don't start with '/'

var obj = {firstName: "Albert"};
var patches = [{op: "add", path: "/lastName", value:"Wester"}];
jsonpatch.apply(obj,patches);
obj = {firstName: 'Albert' },lastName:'Wester' }
If path = "lastName"
The result
{ '0': 'W', '1': 'e', '2': 's', '3': 't', '4': 'e', '5': 'r' }

why?

Problem when observing multiple objects with same structure

When observing multiple obects with the same structure the callback is triggerd on non changed objects.

How to reproduce

<!DOCTYPE html>
<head>
  <title>JSON-Patch : Tester</title>
  <script src="lib/angular.js"></script>
  <script src="lib/json-patch-duplex.js"></script>
</head>

<body ng-app ng-controller="controller">
  <input ng-model="model1.FirstName"/>
  <input ng-model="model1.LastName"/>
  <br>
  <input ng-model="model2.FirstName"/>
  <input ng-model="model2.LastName"/>
</body>

<script>

  function controller($scope) {

      $scope.model1 = { FirstName:"Model A", LastName:"Model A"};
      $scope.model2 = { FirstName:"Model B", LastName:"Model B"};

     // Observe the model
      jsonpatch.observe($scope.model1, function (patches) {
          console.log("model1:observe callback:" + JSON.stringify(patches));
      });

      jsonpatch.observe($scope.model2, function (patches) {
          console.log("model2:observe callback:" + JSON.stringify(patches));
      });

  }

</script>

applying a patch on root fails

TypeError: Cannot call method 'indexOf' of undefined
    at Object.apply (/Users/sonny/Projects/JSON-Patch/src/json-patch.js:129:29

But I'm not sure it makes sense to support the root path.
It seems RFC 6902 lacks a word or too about it 👅 .

  • add and replace ops would replace the complete document
  • test would test for equality as for any other value
  • what about of move, copy and delete?

Adding element to an array should not end with "last index + 1"

Example

var object = {someValues : ["one", "two"]};
var object2 = {someValues : ["one", "two", "new value"]};
var result = jsonpatch.compare(object, object2 );
result is [{"op":"add", "path": "/someValues/2", "value": "new value"}];

If you add an element to an array at the end of it - it should end with "path/-" and not the index of the new element

Incorrect patch on remove array item after changing its property

I think this is related to #65, that I openeed a while ago. But in this case the result is worse as patch is completely wrong. Here's the test case:

it('should generate valid patch on remove array item after changing its property', function() {
      obj = {arr: [{ lastName:"Einstein" }, {lastName:"123"}]};
      var observer = jsonpatch.observe(obj);

      // 2 changes!
      obj.arr[0].lastName = 'Some';
      obj.arr.splice(0, 1);

      patches = jsonpatch.generate(observer);
      expect(patches).toEqual([
        { op: 'remove', path: '/arr/0' }
      ]);
    });

Fails as: Expected [ ] to equal [ { op : 'remove', path : '/arr/0' } ].

Strange behaviour

Code

var a = {as: '234', b: 3};
var observer = jsonpatch.observe(a);
a.b = 4
a.as = 3
a.y = 7
var patches = jsonpatch.generate(observer);

Returns only last change:

[{ op: "add", path: "/y", value: 7 }]

apply() only returns result of last operation

I am using the test operation for some optimistic concurrency, and I noticed that the result from the apply method only takes into consideration the last operation.

From RFC-6902

   If a normative requirement is violated by a JSON Patch document, or
   if an operation is not successful, evaluation of the JSON Patch
   document SHOULD terminate and application of the entire patch
   document SHALL NOT be deemed successful.

   See [RFC5789], Section 2.2 for considerations regarding handling
   errors when JSON Patch is used with the HTTP PATCH method, including
   suggested status codes to use to indicate various conditions.

   Note that the HTTP PATCH method is atomic, as per [RFC5789].
   Therefore, the following patch would result in no changes being made
   to the document at all (because the "test" operation results in an
   error):

   [
     { "op": "replace", "path": "/a/b/c", "value": 42 },
     { "op": "test", "path": "/a/b/c", "value": "C" }
   ]

Therefore the apply should at the very least return false if any operation fails.
Currently the result variable is only ever set and returned.

Patch arrays generated after removing elements differs

If we remove items from an array using Array.prototype.splice() the order of patches generated by jsonpatch.generate() differs, depending on whether jsonpatch.observe() uses native Object.observe or a shim.

Lets take a sample test case:

it('should generate the same patch using Object.observe and shim after removing items from array', 
function() {
      var arr1 = [
        ["Albert", "Einstein"],
        ["Erwin", "Shrodinger"]
      ];

      var arr2 = arr1.slice();

      var observer1 = jsonpatch.observe(arr1);
      arr1.splice(0, 2);

      var objectObservePatches = jsonpatch.generate(observer1);

      var _observe = Object.observe;
      Object.observe = undefined;

      var observer2 = jsonpatch.observe(arr2);
      arr2.splice(0, 2);

      var shimPatches = jsonpatch.generate(observer2);

      expect(objectObservePatches).toEqual(shimPatches); //fails

      Object.observe = _observe;
});

the value stored in objectObservePatches is

[ { op : 'remove', path : '/1' }, { op : 'remove', path : '/0' }, 
{ op : 'replace', path : '/length', value : 0 } ]

whereas shimPatches contains array:

[ { op : 'remove', path : '/0' }, { op : 'remove', path : '/1' } ]

Ignoring patches related to length property (#14) patches generated by Object.observe() version indicate that we first removed item with index 1 and then 0 whereas shim version suggests something opposite.

Add new validation rule: VALUE_OBJECT_CANNOT_CONTAIN_UNDEFINED

(Continued from Palindrom/Palindrom#21)

PuppetJs in debug mode (currently ON by default) validates outgoing patches:

   if(this.debug) {
      this.validateSequence(this.remoteObj, patches);
    }

in which case the error should be reported. I am glad that we both agree :)

@miyconst: Current validation is implemented here: https://github.com/Starcounter-Jack/JSON-Patch/blob/master/src/json-patch-duplex.js#L632

And it is throwing OPERATION_VALUE_REQUIRED for undefined values, which should be replaced with VALUE_OBJECT_CANNOT_CONTAIN_UNDEFINED.

It should not be mixed. The current check for undefined value (OPERATION_VALUE_REQUIRED) should be kept. A new check for undefined property of the value (if the value is an object) should be added after it (VALUE_OBJECT_CANNOT_CONTAIN_UNDEFINED).

@miyconst could you prepare a solution for this on a separate branch in https://github.com/Starcounter-Jack/JSON-Patch? Publish code on a separate branch for review. The changes should include test and update to README.md

Generated patch contains array `length`

In native Object.observe version (Chrome 28 & 30), array length is reported as part of the change records.

I don't think this is an error in Chrome, but I think JSON-Patch should filter out length from it's own generated patch

See http://jsfiddle.net/U4mZJ/3/

For a sample array, generated patch is:

[
 {"op":"replace","path":"/0","value":99},
 {"op":"add","path":"/2","value":3},
 {"op":"replace","path":"/length","value":3}
]

I will fix this soon

Registering the same callback multiple times

I've noticed a quite important inconsistency in the way JSON-Patch handles registering the same callback multiple times, on the same observed object.

It turns out, that registering the same callback multiple times, on the same object using jsonpatch.observe, which takes advantage of native Object.observe function, causes callback to be invoked the same number of times, if any changes occur in the observed object, whereas jsonpatch.observe using Object.observe shim invokes the callback only once, no matter how many times the jsonpatch.observe function was called.

Worth mentioning is a fact, that native Object.observe acts as jsonpatch.observe with shim.

I've prepared a set of test cases: http://jsfiddle.net/Qyb4w/

Ho do you patch your JSON in your projects

Sorry for the question here but I'm very interested in how do you guys patch your JSON document in your projects in production?

How do you store your documents (Redis, pure json files, MongoDB or something else)? Please share your experience or point me right direction.

Fix application of '-' pointer in array

Excerpt from JSON-patch spec (section 4.1):

An element to add to an existing array - whereupon the supplied
      value is added to the array at the indicated location.  Any
      elements at or above the specified index are shifted one position
      to the right.  The specified index MUST NOT be greater than the
      number of elements in the array.  If the "-" character is used to
      index the end of the array (see [RFC6901]), this has the effect of
      appending the value to the array.
var obj = { arr: [ "item 1", "item 2" ] }
var patch = [ { op: 'add', path: '/arr/-', value: 'item 3' } ]

// obj should be { arr: [ "item 1", "item 2", "item 3" ] }
// but is { arr: [ "item 3", "item 1", "item 2" ] }

I think it's reasonable to not generate hyphen patches, but it should be possible to apply them.

I think change to https://github.com/Starcounter-Jack/JSON-Patch/blob/master/src/json-patch.js#L91 would work as follows:

var index = keys[t] === '-' ? obj.length : parseInt(keys[t], 10);

I can create formal pull request with updated tests if you would prefer. What's your build process?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.