starcounter-jack / json-patch Goto Github PK
View Code? Open in Web Editor NEWLean and mean Javascript implementation of the JSON-Patch standard (RFC 6902). Update JSON documents using delta patches.
License: MIT License
Lean and mean Javascript implementation of the JSON-Patch standard (RFC 6902). Update JSON documents using delta patches.
License: MIT License
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.
Chrome 36 introduced native Object.observe
implementation and I think it may be conflicting with yours: http://www.chromestatus.com/features/6147094632988672
I am still researching the issue and will offer more details as I find them.
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.
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' } ].
(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
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.
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/
#35 was merged w.o. tests
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
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>
Support for NPM (#8) is a great addition but it breaks client side usage. test-duplex.html fails at all tests with error Uncaught ReferenceError: exports is not defined
Spec:
http://tools.ietf.org/html/rfc6902#section-4.6
Implementation:
test: function (obj, key) {
return(JSON.stringify(obj[key]) === JSON.stringify(this.value));
},
It doesn't work if 2 arrays/objects are equals (from the RFC definition) but ordered differently.
JSON.stringify(['foo', 'bar']) === JSON.stringify(['bar', 'foo']); //false
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]
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
I.e.
new_obj = apply(obj, patch)
remove(new_obj, path) // == obj
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?
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.
@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'}
console.log(require('fast-json-patch').compare);
//undefined
> jsonpatch.compare({a: {}}, {a: [1]});
[{op: "add", path: "/a/0", value: 1}]
Shouldn't be the empty object replaced with the array? the value is no longer an object.
[{op: "replace", path: "/a", value: [1]}]
tks
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' } ].
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?
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.
Anyone got a copy?
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?
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.
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.
Let me explain my question. I've found livedb project https://github.com/share/livedb that make possible to issue partial updating of big JSON tree with OP supporting.
At now livedb support several ottypes https://github.com/ottypes among which json0 https://github.com/ottypes/json0
Do you plan to contribute this project to allow possibility make OT operations with json-patch?
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"
}
]
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
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
var x = {};
jsonpatch.observe(x, function() {
console.log('One');
});
jsonpatch.observe(x, function() {
console.log('Two');
});
x.someValue = true;
It should execute both functions.
Maybe now is a good time to consider this a 1.0 release
I think that the 0.5 versioning is a bit to conservative. Should we go for 1.0 with the current bits?
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.
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.
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
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 }]
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);
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 documenttest
would test for equality as for any other valuemove
, copy
and delete
?when null replaces an object.
jsonpatch.compare({a: {}}, {a: null});
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?
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"}}
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.
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.
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!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.