-
Notifications
You must be signed in to change notification settings - Fork 217
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
Implement applyPatch to fix #147, with all needed specs #167
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for taking this on!
src/json-patch-duplex.ts
Outdated
* Apply a single json-patch on an object tree | ||
* Returns the result object. | ||
*/ | ||
export function applyPatch(tree: any, patch: any, validate = false): any { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we default validate
to false
if typeof validate !== 'boolean'
? Otherwise the function doesn't work as a reducer because the index is passed as a third parameter and would be coerced to boolean.
src/json-patch-duplex.ts
Outdated
if (patch.path === "") { | ||
if (patch.op === 'add' || patch.op === 'replace') {// same for add or replace | ||
// a primitive value, just return it | ||
if (typeof patch.value !== 'object') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|| patch.value === null
src/json-patch-duplex.ts
Outdated
// a primitive value, just return it | ||
if (typeof patch.value !== 'object') { | ||
return patch.value; | ||
} else if (typeof patch.value === 'object') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this condition never evaluates to false as it is checked above already
src/json-patch-duplex.ts
Outdated
*/ | ||
export function applyPatch(tree: any, patch: any, validate = false): any { | ||
if (validate && typeof tree !== 'object') { | ||
throw new TypeError('Tree has to be an object'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why does the document have to be an object?
src/json-patch-duplex.ts
Outdated
} else { // test operation (even if not on root) needs to return a boolean | ||
if (patch.op === 'test') { | ||
var results = apply(tree, [patch], validate); | ||
return results[0]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This breaks the contract of the function to always return the updated document.
If a test operation fails, it should abort the patch by throwing an error.
If you have an array of patches, it should be possible to use array.reduce()
to apply all patches. That doesn't work if test returns a boolean, but would correctly abort the patch if it throws an error.
src/json-patch-duplex.ts
Outdated
} else { // it's a remove or test on root | ||
if (patch.op === 'test') { | ||
return _equals(tree, patch.value); | ||
} else { // a remove on root |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would check patch.op === 'remove'
here and and either error or ignore the patch (return the input) if the operation is anything else. Otherwise any invalid patch would result in a null
result and it wouldn't be obvious why.
README.md
Outdated
|
||
Applying patches on root is a little tricky, for that you can't replace your root object with an object of a different type in place (eg. can't replace `{item: "item 1"}` with `[{item: "item 1"}]`) on the root level, because the original document is an object `{}` and the new value is an array `[]`, nor can you do the opposite (`[] => {}`), obviously. See https://github.com/Starcounter-Jack/JSON-Patch/issues/147. | ||
|
||
However, you can use `applyPatch` method to over come this issue, because it returns the result object, and this allows it to change types. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be great to document the usage with array.reduce()
to apply multiple patches (that may modify the root).
src/json-patch-duplex.ts
Outdated
} | ||
} else if (patch.op === 'move' || patch.op === 'copy') { // it's a move or copy to root | ||
// get the value by json-pointer in `from` field | ||
var temp: any = { op: "_get", path: patch.from }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these variables are never reassigned so can use const
src/json-patch-duplex.ts
Outdated
* Apply a single json-patch on an object tree | ||
* Returns the result object. | ||
*/ | ||
export function applyPatch(tree: any, patch: any, validate = false): any { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Signature should be
export function applyPatch<T>(tree: T, patch: Patch<any>): T;
export function applyPatch<T>(tree: T, patch: Patch<any>, validate: boolean): T;
export function applyPatch<T>(tree: T, patch: Patch<any>, validate = false): T {
if (typeof validate !== 'boolean') {
validate = false;
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll make it return any
for the true
that is returned by test
op. Agree?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See #167 (comment)
- Enhance typings - Verify if validate is a boolean or default to false - Throw an error when `test` operation fails - Change tests accordingly - Add documentation for using applyPatch with reduce
README.md
Outdated
Applies a single `patch` on `obj`. | ||
|
||
Returns the modified object. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rename it to applyOperationObject
/applyOperation
to be more consistent with naming in spec. Then I'd remove argument and variable inside to operation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW, I'd also adjust the names in apply
, to something like:
Applies JSON
patch
(array of operations) onobj
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not 100% satisfied with the name either, but note that the type for a single operation is also already called Patch
, while apply()
takes an array of that: Patch[]
. Renaming those would be a breaking change. It also matches how the other libraries name these types.
Operation could be a bit confusing because there is the op
attribute, which is short for operation
, that is a string.
In any case I would like to have a short name, applyOperationObject
is very long.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@felixfbecker if I understand correctly, he just means the params/args names, it isn't a breaking change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tomalec I guess you mean 'rename' instead of 'remove' right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know, but imo the name of types and the param names / function name should be consistent. A parameter of type Patch
should be named patch
, not operation
. A function applyOperation
should take a parameter of type Operation
, not Patch
. So I would stay with the "patch" naming meaning a single patch vs calling a single patch "operation".
README.md
Outdated
|
||
Available in *json-patch.js* and *json-patch-duplex.js* | ||
|
||
Applies a single `patch` on `obj`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Applies single operation object patch
on obj
"
I'd get deeper into it after our HO meeting, or later this week.. |
src/json-patch-duplex.ts
Outdated
// conflicting types [] vs {} just return the new value | ||
if (_isArray(tree) !== _isArray(patch.value)) { | ||
return patch.value; | ||
//same type, use original apply |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not a big issue, but why are we making the effort here to mutate the original object at all if the function returns a new reference in some cases anyway? The new object is already in the heap. It is way faster to just switch the reference out than mutating the original object, basically diffing it, removing all properties, then copying over all from the new object. Given the performance focus of this package I would vote for always returning the new value if the root is replaced (it also makes more sense semantically).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree I was building a demo and not modifying the original object seems like a semantic must. Let alone the perf boost.
src/json-patch-duplex.ts
Outdated
} | ||
} | ||
} | ||
} else { // test operation (even if not on root) needs to return a boolean |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
comment is outdated
@felixfbecker I guess we were both off with our definition of
So this: export interface AddPatch<T> extends PatchBase {
op: 'add';
value: T;
} Isn't a correct interface for a patch 😢 It is correct for operation, though. |
I am aware and not happy with it either, but again it would be a breaking change to change. What we could do is export the old types as type aliases and deprecate them though. |
@alshakero @tomalec Wdyt about
|
@felixfbecker Sounds great. Let's see @tomalec's opinion. In the meantime, I'll push now some changes I did to |
- Turn to opt-in pure function - Better documentation - Needed tests
src/json-patch-duplex.ts
Outdated
*/ | ||
export function applyPatch<T>(tree: T, patch: Patch<any>): T; | ||
export function applyPatch<T>(tree: T, patch: Patch<any>, validate: boolean, touchOriginalTree: boolean): T; | ||
export function applyPatch<T>(tree: T, patch: Patch<any>, validate = false, touchOriginalTree = true): T { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not convinced we should have the third parameter, it bloats the signature and adds more branches to the function (you would have to test this for all operations). JSON Patch's sole purpose is to mutate objects. If you really want to clone the tree on every operation, it can be easily achieved through functional composition:
patches.reduce((obj, op) => applyOperation(deepClone(obj), op), obj)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But this makes the function inconsistently impure. And we have a use case for the pure version already. I don't mind writing tests for more cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want to keep it, the signature needs to be updated to make touchOriginalTree
optional
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@felixfbecker doesn't this signature alone suffice?
export function applyPatch<T>(tree: T, patch: Patch<any>, validate: boolean = false, touchOriginalTree: boolean = true): T {
if (typeof validate !== 'boolean') { // if validate is not a boolean, it's being called from `reduce`
validate = false;
touchOriginalTree = true;
}
According to TS docs, any param with a default value is optional.
Default-initialized parameters that come after all required parameters are treated as optional, and just like optional parameters, can be omitted when calling their respective function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, because that is just the implementation. This signature is overloaded, so the two signatures above the implementation that just end with a semicolon define the signature. Overloading is needed here because without the first signature the function would not work as reducer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To clarify, the second signature needs to be updated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
export function applyPatch<T>(tree: T, patch: Patch<any>): T;
export function applyPatch<T>(tree: T, patch: Patch<any>, validate: boolean, touchOriginalTree?: boolean): T;
export function applyPatch<T>(tree: T, patch: Patch<any>, validate = false, touchOriginalTree = true): T {
if (typeof validate !== 'boolean') { // if validate is not a boolean, it's being called from `reduce`
validate = false;
touchOriginalTree = true;
}
}
This does it, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, LGTM
src/json-patch-duplex.ts
Outdated
} else if (patch.op === 'move' || patch.op === 'copy') { // it's a move or copy to root | ||
// get the value by json-pointer in `from` field | ||
const temp: any = { op: "_get", path: patch.from }; | ||
apply(tree, [temp]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
copy and move should also just return the new value
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They do return?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah sorry, just saw that the apply
is only for getting the value 👍
I'd just change the docs to match exact RFC naming so "that it applies a single operation object and returns the updated document" |
@felixfbecker is there a chance that you do the type aliasing thing? That would be a huge favour because you're the most fluent TypeScripter here. |
Yeah no problem |
Rename Patch to Operation
- Make `applyOperation` standalone (#175) - Deprecate `apply` and throw a warning when called - Fix `_equals` (#166) - Replace parseInt with bitwise opers (#171) - Export `escapePath` and `unEscapePath`(#173) - Add `applyReduer` to use with Array.Reduce - Remove `rootOps` - Add tests for all new functions - Add backwards compability tests - Update readme accordingly
@tomalec Ready for review. |
src/json-patch.ts
Outdated
export interface PatchBase { | ||
export interface OperationResult { | ||
result: any, | ||
newDocument: any |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be generic T
src/json-patch.ts
Outdated
* @param path The escaped pointer | ||
* @return The unescaped path | ||
*/ | ||
export function unEscapePath(path: string): string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The native unescape
function does not uppercase the E
src/json-patch.ts
Outdated
* @param pointer an escaped JSON pointer | ||
* @return The retrieved value | ||
*/ | ||
export function getValueByPointer(document, pointer) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(document: any, pointer: string): any
src/json-patch.ts
Outdated
* @param mutateDocument Whether to mutate the original document or clone it before applying | ||
* @return `{newDocument, result}` after the operation | ||
*/ | ||
export function applyOperation<T>(document: any, operation: Operation, validate: boolean, mutateDocument: boolean): OperationResult; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this overload is unneeded
README.md
Outdated
* `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) | ||
|
||
#### jsonpatch.observe (`obj` Object, `callback` Function (optional)) : `observer` Object | ||
#### `jsonpatch.applyOperation<T>(document: T, operation: Operation, validate: boolean = false, mutateDocument: boolean = false): T` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
signature is outdated
src/json-patch.ts
Outdated
} | ||
} /* END ROOT OPERATIONS */ | ||
else { | ||
!mutateDocument && (document = deepClone(document)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
statement syntax (if) is more readable for side effects than expression syntax imo
src/json-patch.ts
Outdated
value = jsonpatch.getValueByPointer(document, (<any>patch[i]).from); | ||
} | ||
if (patch[i].op == "replace" || patch[i].op == "add") { | ||
value = (<any>patch[i]).value; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TypeScript should infer the type as ReplaceOperation | AddOperation
if you use ===
instead of ==
, you shouldn't need to cast to any
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't work, still complaining. I'm using TS 2.0.0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you try upgrading?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, upgrading breaks compilation if you remember. Fixing the issue needs some investigation and it's not a priority for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I though it might have been fixed. I would cast to ReplaceOperation | AddOperation
then
src/json-patch.ts
Outdated
|
||
} | ||
else { | ||
results[i] = applyOperation.call(this, document, patch[i], validate, true).result; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why .call(this, ...
? What is this
even in this context? What does applyOperation
need it for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It needs it for this.validator
.
src/json-patch.ts
Outdated
* @param operation The operation to apply | ||
* @return The updated document | ||
*/ | ||
export function applyReducer<T>(document: T, operation: Operation): T; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
redundant signature
src/json-patch.ts
Outdated
*/ | ||
export function applyReducer<T>(document: T, operation: Operation): T; | ||
export function applyReducer<T>(document: T, operation: Operation): T { | ||
const operationResult: OperationResult = applyOperation.call(this, document, operation) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why .call(this, ...
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same reason, it needs this
for this.validator
.
src/json-patch.ts
Outdated
export function applyOperation<T>(document: any, operation: Operation, validate: boolean, mutateDocument: boolean): OperationResult; | ||
export function applyOperation<T>(document: any, operation: Operation, validate = false, mutateDocument = true): OperationResult { | ||
if (validate) { | ||
this.validator(operation, 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wait, so if you just call applyValidation(document, op, true)
the function will throw? Why does the user have to provide this as a this
binding, instead of an optional parameter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really because it is bound to jsonpatch
object by default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As in
var jsonpatch;
(function (jsonpatch) {
function sub() {
return this;
}
jsonpatch.sub = sub;
})(jsonpatch = {});
jsonpatch == jsonpatch.sub(); // ==> true
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if I import it like this
import { applyOperation } from 'fast-json-patch'
and transpile with Babel, or let Webpack do tree-shaking? It probably won't pick up the reference. This feels like a hack
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or in ES5:
const { applyOperation } = require('fast-json-patch')
const applyOperation = require('fast-json-patch').applyOperation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer keeping the API as close as possible as I can to its current shape. Especially when it comes to often-used functions. Setting the validator for once sounds better.
A .setValidator
function or a setter sound better, and I'll ask my teammates to maybe throw this whole thing. I don't see any use for it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tbh I would consider that a code smell. It's a global setting that could be set anywhere in an application (even by a library) and would yield unexpected results. How would a library customize the validator without affecting application code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
100% legit point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would consider that a code smell.
Totally agree.
I'm for the optional param: validate: boolean | Validator
, where
false
- no validationtrue
- default validator:jsonpatch.validator
function
- custom validator
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perfect then 💃
README.md
Outdated
@@ -92,29 +92,24 @@ var patch = [ | |||
{ op: "add", path: "/lastName", value: "Wester" }, | |||
{ op: "add", path: "/contactDetails/phoneNumbers/0", value: { number: "555-123" } } | |||
]; | |||
jsonpatch.apply(document, patch); | |||
document = jsonpatch.applyPatch(document, patch).document; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the old jsonpatch.applyPatch(document, patch)
should work a before, do we need to obfuscate the example code?
README.md
Outdated
|
||
If the `validate` parameter is set to `true`, the patch is extensively validated before applying. | ||
An invalid patch results in throwing an error (see `jsonpatch.validate` for more information about the error object). | ||
|
||
Returns an array of results - one item for each item in `patches`. The type of each item depends on type of operation applied | ||
Returns an array of objects - one item for each item in `patches`, each item is an object `{newDocument, result}`. The type of `result` depends on type of operation applied. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe one item for each operation in
patches, each item is an object
to reduce the number of "items"
README.md
Outdated
TEST_OPERATION_FAILED | When operation is `test` and the test fails, applies to `applyReducer`. | ||
|
||
|
||
#### `jsonpatch.escapePath(path: string): string` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't it a duplicate of the same thing written above?
I'll continue review, later, had to 'finish' it to make github allow me to respond to your comments. |
Can you elaborate on
Yes:
I don't mind that as a next step, but for now, I guess we should keep warning people to use |
- Add `.newDocument` to applyPatch return value and readme - A little cleanup
I like
Is there a reason |
@felixfbecker it is changed to a |
src/json-patch-duplex.ts
Outdated
for (var i = 0, l = a.length; i < l; i++) | ||
if (!_equals(a[i], b[i])) return false; | ||
|
||
if (!_equals(a[i], b[i])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add {}
after if
?
src/json-patch-duplex.ts
Outdated
var original = getOriginalDestination.value === undefined ? | ||
undefined : JSON.parse(JSON.stringify(getOriginalDestination.value)); | ||
move: function (obj, key, document) { | ||
const originalValue = getValueByPointer(document, this.path); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't you use result
of const originalValue = applyOperation(document, { op: "remove", path: this.from } ).result;
so you don't have to apply _get
here and iterate over tree once again?
Speaking of naming of I would consider old behavior buggy and would like to drop support for it sooner than later. Speaking of performance of Speaking of results: const removeOutcome = jsonpatch.applyOperation(document, { op: "remove", path: 'path/foo' } );
removeOutcome: {
document: document,
removed: 'bar'
};
// replace, move, copy are in fact remove + add
const testOutcome = jsonpatch.applyOperation(document, { op: "test", path: 'patt/foo', value: 'baz' } );
testOutcome: {
document: document,
test: true
}; |
I would say either
|
I'd keep buggy
As big as this may sound. It's exactly two hours for me, I don't even mind doing it in this PR today. With benchmarking clone lib vs our deepClone. If you agree, I can go for it and we can remove
I did, it's back as it was 💃 Regarding results, I'm working on it. |
I would vote for keeping a minor and not bloating this PR any further. |
@felixfbecker Me too. I'm just trying my best to point out that it's not going to take a lot of time to implement it. And that we need a major release for something else. |
- Update tests to follow. - Explain `OperationResult` clearly in README. - Return overwritten value in `move` operation.
Thanks, now I feel convinced :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should also return result.removed
for add
operation, as it may replace:
https://tools.ietf.org/html/rfc6902#section-4.1
If the target location specifies an object member that does exist,
that member's value is replaced.
src/json-patch-duplex.js
Outdated
return originalValue; | ||
var overwrittenValue = getValueByPointer(document, this.path); | ||
if (overwrittenValue) { | ||
overwrittenValue = deepClone(overwrittenValue); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to clone for move?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is how it originally behaved. In case the move target was overwritten we need to keep a copy of it to return it as removed. I can remove this but there is a spec for it and I thought it's necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, fair point.
However, I'm not 100% sure if it's very useful, but it could be expensive for large object.
Could you add short code comment, jsdoc, or readme node why it's needed? We could discuss it later if we would like to improve performance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure! A couple minutes.
src/json-patch-duplex.js
Outdated
} | ||
} | ||
else { | ||
debugger; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove this debugger
src/json-patch-duplex.js
Outdated
} | ||
else { | ||
debugger; | ||
returnValue.removed = arrOps[operation.op].call(operation, obj, key, document); // Apply patch |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be cleaner if each arrOps[*]
would return applicable object {remove: smth}
or {test: true}
so you will not have to check it once again here.
src/json-patch-duplex.js
Outdated
} | ||
} | ||
else { | ||
debugger; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please remove debugger
src/json-patch-duplex.js
Outdated
} | ||
else { | ||
debugger; | ||
returnValue.removed = objOps[operation.op].call(operation, obj, key, document); // Apply patch |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above. Maybe we don't need this if at all?
src/json-patch-duplex.js
Outdated
@@ -593,7 +606,9 @@ var jsonpatch; | |||
Object.keys(value_1).forEach(function (key) { return document[key] = value_1[key]; }); | |||
} | |||
else { | |||
results[i] = applyOperation(document, patch[i], validateOperation, true).result; | |||
results[i] = applyOperation(document, patch[i], validateOperation); | |||
debugger; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
debugger
once again
src/json-patch-duplex.js
Outdated
results[i] = applyOperation(document, patch[i], validateOperation, true).result; | ||
results[i] = applyOperation(document, patch[i], validateOperation); | ||
debugger; | ||
results[i] = results[i].removed || results[i].test; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can the results array be an array of result objects?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really, because this is deprecated apply.
src/json-patch-duplex.js
Outdated
else { | ||
debugger; | ||
returnValue.removed = arrOps[operation.op].call(operation, obj, key, document); // Apply patch | ||
returnValue = arrOps[operation.op].call(operation, obj, key, document); // Apply patch |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now, we don't need to create returnValue
above, to replace it here .
@tomalec shall I merge? |
src/json-patch-duplex.ts
Outdated
results[i] = applyOperation(document, patch[i], validateOperation); | ||
document = results[i].newDocument; // in case root was replaced | ||
} | ||
(<any>results).newDocument = document; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be reflected in the return type, e.g.
export interface PatchResult<T> extends Array<OperationResult<T>> {
newDocument: T;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And initialize results array like this?
const results = (<PatchResult<T>>new Array(patch.length));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, the cast is needed since at the time of initialising newDocument
is not set even though it's not optional. You can also write the slightly prettier
const results = new Array(patch.length) as PatchResult<T>;
Btw, is there a perf benefit in using the array constructor? I once read that array literals are faster
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to this, the constructor is ~200% faster.
https://jsperf.com/array-constructor-vs-literal-modalities
Fixes: #147