Palindrom / JSONPatcherProxy

ES6 proxy powered JSON Object observer that emits JSON patches when changes occur to your object tree.
94 stars 14 forks source link

Don't generate patch for a no-change set operation #45

Closed eriksunsol closed 5 years ago

eriksunsol commented 5 years ago

Extending the example in README.md like this:

var myObj = { firstName:"Joachim", lastName:"Wester", contactDetails: { phoneNumbers: [ { number:"555-123" }] } };
var jsonPatcherProxy = new JSONPatcherProxy( myObj );
var observedObject = jsonPatcherProxy.observe(true);
observedObject.firstName = "Albert";
observedObject.firstName = "Albert";
observedObject.firstName = "Albert";
observedObject.contactDetails.phoneNumbers[0].number = "123";
observedObject.contactDetails.phoneNumbers.push({number:"456"});
var patches = jsonPatcherProxy.generate();

results in the following patch:

[
  {"op":"replace","path":"/firstName","value":"Albert"},
  {"op":"replace","path":"/firstName","value":"Albert"},
  {"op":"replace","path":"/firstName","value":"Albert"},
  {"op":"replace","path":"/contactDetails/phoneNumbers/0/number","value":"123"},
  {"op":"add","path":"/contactDetails/phoneNumbers/1","value":{"number":"456"}}
]

The second and third time firstName is set are effectively no-ops which have already been communicated. I think this is wrong. The proposed change recognizes this and renders the following result instead:

[
  {"op":"replace","path":"/firstName","value":"Albert"},
  {"op":"replace","path":"/contactDetails/phoneNumbers/0/number","value":"123"},
  {"op":"add","path":"/contactDetails/phoneNumbers/1","value":{"number":"456"}}
]
eriksunsol commented 5 years ago

Tests added. I have also worked through no-change operations on array elements and made sure undefined and null are treated properly with respect to their JSON projections in objects and arrays (which is slightly different). I took the liberty to add a test or two which are variants of tests that already existed, but for the special cases e.g. when an undefined property is set to null. This is to make sure testing for no-change operations do not catch cases which should be left alone.

tomalec commented 5 years ago

@warpech suggested to make return Reflect.set(tree, key, newValue); DRYier

warpech commented 5 years ago

I will make the required changes in a new PR.

warpech commented 5 years ago

@warpech suggested to make return Reflect.set(tree, key, newValue); DRYier

@tomalec I have made two alternative solutions:

  1. single Reflect.set before the big pile of IFs with many return statements: https://github.com/Palindrom/JSONPatcherProxy/commit/43f3439dd5b43a1d9eb7f348074b3f76c4958e8c
  2. single Reflect.set after the big pile of IFs with single return statement: https://github.com/Palindrom/JSONPatcherProxy/commit/71078c341ad83d8313c8c13a65178f5f9ba8ece6

The code is quite complex, and I am not very happy with the readability of either.

Any suggestions which is better? (I can accept "Current" as an answer, too)

tomalec commented 5 years ago

I have a slight preference over 1. because of readable variable names wasKeyInTreeBeforeReflection, valueBeforeReflection and the fact that whether the callback/following code is executed is controlled using standard return, not enigmatic operation.op

warpech commented 5 years ago

As discussed today between @eriksunsol, @tomalec and me, I have pushed my solution that fixes the test created by @eriksunsol (https://github.com/Palindrom/JSONPatcherProxy/pull/45/commits/0257a923ad761dc0ec9507c869039f57d7142170).

@tomalec, @eriksunsol could you pls check and give your blessing?

I plan to add more tests from https://github.com/Starcounter-Jack/JSON-Patch/tree/master/test in a separate PR before next release.

warpech commented 5 years ago

We found another issue with this function. Is it called when you pop an array? Then change from undefined(mapped to null) to not existing element. I guess it should call deleteTrap but it's worth to add a test for such case as well.

Let's continue that in https://github.com/Palindrom/JSONPatcherProxy/issues/52