├── .gitignore
├── .npmignore
├── .travis.yml
├── CONTRIBUTING.md
├── LICENSE.txt
├── README.md
├── bower.json
├── commonjs
├── core.js
├── duplex.js
└── helpers.js
├── dist
├── fast-json-patch.js
└── fast-json-patch.min.js
├── index.d.ts
├── index.js
├── index.mjs
├── index.ts
├── jasmine-run.mjs
├── module
├── core.d.ts
├── core.mjs
├── duplex.d.ts
├── duplex.mjs
├── helpers.d.ts
└── helpers.mjs
├── package-lock.json
├── package.json
├── src
├── core.ts
├── duplex.ts
└── helpers.ts
├── test
├── Sauce
│ ├── CapabilityRunner.js
│ └── Runner.js
├── index.html
├── jasmine.json
├── lib
│ ├── benchmark_console_reporter.js
│ ├── benchmark_reporter.css
│ └── benchmark_reporter.js
└── spec
│ ├── commonjs
│ └── requireSpec.js
│ ├── coreBenchmark.js
│ ├── coreSpec.mjs
│ ├── duplexBenchmark.js
│ ├── duplexSpec.mjs
│ ├── json-patch-tests
│ ├── .gitignore
│ ├── README.md
│ ├── spec_tests.json.mjs
│ └── tests.json.mjs
│ ├── jsonPatchTestsSpec.mjs
│ ├── typings
│ └── typingsSpec.ts
│ ├── validateSpec.mjs
│ └── webpack
│ ├── importSpec.build.js
│ └── importSpec.src.js
├── tsc-to-mjs.sh
├── tsconfig.json
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Mac OS X
2 | .DS_Store
3 |
4 | # Node.js
5 | node_modules/*
6 | npm-debug.log
7 |
8 | # VS Code
9 | .vscode/*
10 | !.vscode/settings.json
11 | !.vscode/tasks.json
12 | !.vscode/launch.json
13 | !.vscode/extensions.json
14 | *.code-workspace
15 |
16 | # Local History for Visual Studio Code
17 | .history/
18 |
19 | # WebStorm
20 | .idea
21 |
22 | # Typescript
23 | commonjs/*.d.ts
24 | test/spec/typings/typingsSpec.js
25 |
26 | # SauceLabs logs
27 | *.log
28 |
29 | .artifacts
30 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | Makefile
2 | doc/
3 | examples/
4 | test/
5 | .travis.yml
6 | !*.d.ts
7 | src/*
8 | tsconfig.json
9 |
10 | # Mac OS X
11 | .DS_Store
12 |
13 | # Node.js
14 | .npmignore
15 | node_modules/
16 | npm-debug.log
17 | CONTRIBUTING.md
18 |
19 | # Git
20 | .git*
21 |
22 | # bower
23 | bower.json
24 |
25 | # grunt
26 | gruntfile.js
27 |
28 | #vs-code
29 | .vscode/*
30 | !.vscode/settings.json
31 | !.vscode/tasks.json
32 | !.vscode/launch.json
33 | !.vscode/extensions.json
34 | *.code-workspace
35 |
36 | # Local History for Visual Studio Code
37 | .history/
38 |
39 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | dist: trusty
3 | node_js: node
4 | before_script:
5 | - npm install
6 | - npm run serve &
7 | script:
8 | - npm run test
9 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | ## Making changes in TypeScript
4 |
5 | Make sure that you edit the source files in TypeScript (`*.ts`) files.
6 |
7 | To install the TypeScript compiler, run `npm install -g typescript`.
8 |
9 | You can build TypeScript to JavaScript using the [`TypeScript compiler`](https://www.typescriptlang.org/docs/tutorial.html)
10 | (and run it with npm script: `npm run tsc`).
11 |
12 | ## Testing
13 |
14 | ### In a web browser
15 |
16 | #### Testing
17 |
18 | - Load `test/` in your web browser
19 |
20 | Each of the test suite files contains *Jasmine* unit test suite and *Benchmark.js* performance test suite.
21 |
22 | To run *Benchmark.js* performance tests, press "Run Tests" button.
23 |
24 | ### In Node.js
25 |
26 | 1. Go to directory where you have cloned the repo
27 | 2. Install dev dependencies (Jasmine Node.js module) by running command `npm install`
28 | 3. Run test `npm run test`
29 | - Testing **`core`** only: `npm run test-core`
30 | - Testing **`duplex`** only: `npm run test-duplex`
31 | 4. Run test `npm run bench` (Please, consider performance when making any change)
32 | - Testing **`core*`* only: `npm run bench-core`
33 | - Testing **`duplex`** only: `npm run bench-duplex`
34 |
35 |
36 | ## Releasing a new version
37 |
38 | **The release is done from `master` branch.**
39 |
40 | 1. Don't break too much. See [how many projects depend on this](https://www.npmjs.com/browse/depended/fast-json-patch).
41 | 2. Make sure that the browser tests pass in Chrome, Firefox, Safari, Edge and IE11
42 | 3. Make sure that the NodeJS tests pass `npm install && npm run test`
43 | 4. Execute `npm run build` to transpile, bundle and minify.
44 | 5. Execute `npm version` like (`npm version [ major | minor | patch | premajor | preminor | prepatch | prerelease]`)
45 | 6. Call `git push` to push the changes to `origin master`
46 | 7. Call `git push --tags` to push the tag to `origin master`
47 | 8. Call `npm publish` to push the new version to NPM. [Read more](https://docs.npmjs.com/getting-started/publishing-npm-packages)
48 | 9. Call `npm view fast-json-patch dist-tags` to verify that the new version was published in NPM.
49 | 10. Explain the changes (at least a summary of the commit log) in [GitHub Releases](https://github.com/Starcounter-Jack/JSON-Patch/releases).
50 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | (The MIT License)
2 |
3 | Copyright (c) 2013, 2014, 2020 Joachim Wester
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | 'Software'), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | JSON-Patch
2 | ===============
3 |
4 | > A leaner and meaner implementation of JSON-Patch. Small footprint. High performance.
5 |
6 | [](https://travis-ci.org/Starcounter-Jack/JSON-Patch)
7 |
8 | With JSON-Patch, you can:
9 | - **apply** patches (arrays) and single operations on JS object
10 | - **validate** a sequence of patches
11 | - **observe** for changes and **generate** patches when a change is detected
12 | - **compare** two objects to obtain the difference
13 |
14 | Tested in Firefox, Chrome, Edge, Safari, IE11, Deno and Node.js
15 |
16 |
17 | ## Why you should use JSON-Patch
18 |
19 | JSON-Patch [(RFC6902)](http://tools.ietf.org/html/rfc6902) is a standard format that
20 | allows you to update a JSON document by sending the changes rather than the whole document.
21 | JSON Patch plays well with the HTTP PATCH verb (method) and REST style programming.
22 |
23 | Mark Nottingham has a [nice blog]( http://www.mnot.net/blog/2012/09/05/patch) about it.
24 |
25 |
26 | ## Install
27 |
28 | [Download as ZIP](https://github.com/Starcounter-Jack/JSON-Patch/archive/master.zip) or install the current version using a package manager (and save it as a dependency):
29 |
30 | ```sh
31 | # NPM
32 | npm install fast-json-patch --save
33 | ```
34 |
35 |
36 | ## Adding to your project
37 |
38 | ### In a web browser
39 |
40 | Load the bundled distribution script:
41 |
42 | ```html
43 |
44 | ```
45 |
46 | In [browsers that support ECMAScript modules](https://caniuse.com/#feat=es6-module), the below code uses this library as a module:
47 |
48 | ```html
49 |
53 | ```
54 |
55 | ### In Node.js
56 |
57 | In Node 12+ with `--experimental-modules` flag, the below code uses this library as an ECMAScript module:
58 |
59 | ```js
60 | import * as jsonpatch from 'fast-json-patch/index.mjs';
61 | import { applyOperation } from 'fast-json-patch/index.mjs';
62 | ```
63 |
64 | In Webpack (and most surely other bundlers based on Babel), the below code uses this library as an ECMAScript module:
65 |
66 | ```js
67 | import * as jsonpatch from 'fast-json-patch';
68 | import { applyOperation } from 'fast-json-patch';
69 | ```
70 |
71 | In standard Node, the below code uses this library as a CommonJS module:
72 |
73 | ```js
74 | const { applyOperation } = require('fast-json-patch');
75 | const applyOperation = require('fast-json-patch').applyOperation;
76 | ```
77 |
78 | ## Directories
79 |
80 | Directories used in this package:
81 |
82 | - `dist/` - contains ES5 files for a Web browser
83 | - `commonjs/` - contains CommonJS module and typings
84 | - `module/` - contains ECMAScript module and typings
85 | - `src/` - contains TypeScript source files
86 |
87 | ## API
88 |
89 | #### `function applyPatch(document: T, patch: Operation[], validateOperation?: boolean | Validator, mutateDocument: boolean = true, banPrototypeModifications: boolean = true): PatchResult`
90 |
91 | Applies `patch` array on `obj`.
92 |
93 | - `document` The document to patch
94 | - `patch` a JSON-Patch array of operations to apply
95 | - `validateOperation` Boolean for whether to validate each operation with our default validator, or to pass a validator callback
96 | - `mutateDocument` Whether to mutate the original document or clone it before applying
97 | - `banPrototypeModifications` Whether to ban modifications to `__proto__`, defaults to `true`.
98 |
99 | An invalid patch results in throwing an error (see `jsonpatch.validate` for more information about the error object).
100 |
101 | It modifies the `document` object and `patch` - it gets the values by reference.
102 | If you would like to avoid touching your `patch` array values, clone them: `jsonpatch.applyPatch(document, jsonpatch.deepClone(patch))`.
103 |
104 | Returns an array of [`OperationResult`](#operationresult-type) objects - one item for each item in `patches`, each item is an object `{newDocument: any, test?: boolean, removed?: any}`.
105 |
106 | * `test` - boolean result of the test
107 | * `remove`, `replace` and `move` - original object that has been removed
108 | * `add` (only when adding to an array) - index at which item has been inserted (useful when using `-` alias)
109 |
110 | - ** Note: It throws `TEST_OPERATION_FAILED` error if `test` operation fails. **
111 | - ** Note II: the returned array has `newDocument` property that you can use as the final state of the patched document **.
112 | - ** Note III: By default, when `banPrototypeModifications` is `true`, this method throws a `TypeError` when you attempt to modify an object's prototype.
113 |
114 | - See [Validation notes](#validation-notes).
115 |
116 | Example:
117 |
118 | ```js
119 | var document = { firstName: "Albert", contactDetails: { phoneNumbers: [] } };
120 | var patch = [
121 | { op: "replace", path: "/firstName", value: "Joachim" },
122 | { op: "add", path: "/lastName", value: "Wester" },
123 | { op: "add", path: "/contactDetails/phoneNumbers/0", value: { number: "555-123" } }
124 | ];
125 | document = jsonpatch.applyPatch(document, patch).newDocument;
126 | // document == { firstName: "Joachim", lastName: "Wester", contactDetails: { phoneNumbers: [{number:"555-123"}] } };
127 | ```
128 |
129 | #### `function applyOperation(document: T, operation: Operation, validateOperation: boolean | Validator = false, mutateDocument: boolean = true, banPrototypeModifications: boolean = true, index: number = 0): OperationResult`
130 |
131 | Applies single operation object `operation` on `document`.
132 |
133 | - `document` The document to patch
134 | - `operation` The operation to apply
135 | - `validateOperation` Whether to validate the operation, or to pass a validator callback
136 | - `mutateDocument` Whether to mutate the original document or clone it before applying
137 | - `banPrototypeModifications` Whether to ban modifications to `__proto__`, defaults to `true`.
138 | - `index` The index of the operation in your patch array. Useful for better error reporting when that operation fails to apply.
139 |
140 | It modifies the `document` object and `operation` - it gets the values by reference.
141 | If you would like to avoid touching your values, clone them: `jsonpatch.applyOperation(document, jsonpatch.deepClone(operation))`.
142 |
143 | Returns an [`OperationResult`](#operationresult-type) object `{newDocument: any, test?: boolean, removed?: any}`.
144 |
145 | - ** Note: It throws `TEST_OPERATION_FAILED` error if `test` operation fails. **
146 | - ** Note II: By default, when `banPrototypeModifications` is `true`, this method throws a `TypeError` when you attempt to modify an object's prototype.
147 |
148 | - See [Validation notes](#validation-notes).
149 |
150 | Example:
151 |
152 | ```js
153 | var document = { firstName: "Albert", contactDetails: { phoneNumbers: [] } };
154 | var operation = { op: "replace", path: "/firstName", value: "Joachim" };
155 | document = jsonpatch.applyOperation(document, operation).newDocument;
156 | // document == { firstName: "Joachim", contactDetails: { phoneNumbers: [] }}
157 | ```
158 |
159 | #### `jsonpatch.applyReducer(document: T, operation: Operation, index: number): T`
160 |
161 | **Ideal for `patch.reduce(jsonpatch.applyReducer, document)`**.
162 |
163 | Applies single operation object `operation` on `document`.
164 |
165 | Returns the a modified document.
166 |
167 | Note: It throws `TEST_OPERATION_FAILED` error if `test` operation fails.
168 |
169 | Example:
170 |
171 | ```js
172 | var document = { firstName: "Albert", contactDetails: { phoneNumbers: [ ] } };
173 | var patch = [
174 | { op:"replace", path: "/firstName", value: "Joachim" },
175 | { op:"add", path: "/lastName", value: "Wester" },
176 | { op:"add", path: "/contactDetails/phoneNumbers/0", value: { number: "555-123" } }
177 | ];
178 | var updatedDocument = patch.reduce(applyReducer, document);
179 | // updatedDocument == { firstName:"Joachim", lastName:"Wester", contactDetails:{ phoneNumbers[ {number:"555-123"} ] } };
180 | ```
181 |
182 | #### `jsonpatch.deepClone(value: any): any`
183 |
184 | Returns deeply cloned value.
185 |
186 | #### `jsonpatch.escapePathComponent(path: string): string`
187 |
188 | Returns the escaped path.
189 |
190 | #### `jsonpatch.unescapePathComponent(path: string): string`
191 |
192 | Returns the unescaped path.
193 |
194 | #### `jsonpatch.getValueByPointer(document: object, pointer: string)`
195 |
196 | Retrieves a value from a JSON document by a JSON pointer.
197 |
198 | Returns the value.
199 |
200 | #### `jsonpatch.observe(document: any, callback?: Function): Observer`
201 |
202 | Sets up an deep observer on `document` that listens for changes in object tree. When changes are detected, the optional
203 | callback is called with the generated patches array as the parameter.
204 |
205 | Returns `observer`.
206 |
207 | #### `jsonpatch.generate(document: any, observer: Observer, invertible = false): Operation[]`
208 |
209 | If there are pending changes in `obj`, returns them synchronously. If a `callback` was defined in `observe`
210 | 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.
211 |
212 | If there are no pending changes in `obj`, returns an empty array (length 0).
213 |
214 | Example:
215 |
216 | ```js
217 | var document = { firstName: "Joachim", lastName: "Wester", contactDetails: { phoneNumbers: [ { number:"555-123" }] } };
218 | var observer = jsonpatch.observe(document);
219 | document.firstName = "Albert";
220 | document.contactDetails.phoneNumbers[0].number = "123";
221 | document.contactDetails.phoneNumbers.push({ number:"456" });
222 | var patch = jsonpatch.generate(observer);
223 | // patch == [
224 | // { op: "replace", path: "/firstName", value: "Albert"},
225 | // { op: "replace", path: "/contactDetails/phoneNumbers/0/number", value: "123" },
226 | // { op: "add", path: "/contactDetails/phoneNumbers/1", value: {number:"456"}}
227 | // ];
228 | ```
229 |
230 | Example of generating patches with test operations for values in the first object:
231 |
232 | ```js
233 | var document = { firstName: "Joachim", lastName: "Wester", contactDetails: { phoneNumbers: [ { number:"555-123" }] } };
234 | var observer = jsonpatch.observe(document);
235 | document.firstName = "Albert";
236 | document.contactDetails.phoneNumbers[0].number = "123";
237 | document.contactDetails.phoneNumbers.push({ number:"456" });
238 | var patch = jsonpatch.generate(observer, true);
239 | // patch == [
240 | // { op: "test", path: "/firstName", value: "Joachim"},
241 | // { op: "replace", path: "/firstName", value: "Albert"},
242 | // { op: "test", path: "/contactDetails/phoneNumbers/0/number", value: "555-123" },
243 | // { op: "replace", path: "/contactDetails/phoneNumbers/0/number", value: "123" },
244 | // { op: "add", path: "/contactDetails/phoneNumbers/1", value: {number:"456"}}
245 | // ];
246 | ```
247 |
248 | #### `jsonpatch.unobserve(document, observer)`
249 | ```typescript
250 | jsonpatch.unobserve(document: any, observer: Observer): void
251 |
252 | type JsonableObj = { [key:string]: Jsonable };
253 | type JsonableArr = Jsonable[];
254 | type Jsonable = JsonableArr | JsonableObj | string | number | boolean | null;
255 | ```
256 |
257 | Destroys the observer set up on `document`.
258 |
259 | Any remaining changes are delivered synchronously (as in `jsonpatch.generate`). Note: this is different that ES6/7 `Object.unobserve`, which delivers remaining changes asynchronously.
260 |
261 | #### `jsonpatch.compare(document1, document2, invertible)`
262 |
263 | ```typescript
264 | jsonpatch.compare(document1: Jsonable, document2: Jsonable, invertible = false): Operation[]
265 |
266 | type JsonableObj = { [key:string]: Jsonable };
267 | type JsonableArr = Jsonable[];
268 | type Jsonable = JsonableArr | JsonableObj | string | number | boolean | null;
269 | ```
270 |
271 | 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`.
272 |
273 | If there are no differences, returns an empty array (length 0).
274 |
275 | Example:
276 |
277 | ```js
278 | var documentA = {user: {firstName: "Albert", lastName: "Einstein"}};
279 | var documentB = {user: {firstName: "Albert", lastName: "Collins"}};
280 | var diff = jsonpatch.compare(documentA, documentB);
281 | //diff == [{op: "replace", path: "/user/lastName", value: "Collins"}]
282 | ```
283 |
284 | Example of comparing two object trees with test operations for values in the first object:
285 |
286 | ```js
287 | var documentA = {user: {firstName: "Albert", lastName: "Einstein"}};
288 | var documentB = {user: {firstName: "Albert", lastName: "Collins"}};
289 | var diff = jsonpatch.compare(documentA, documentB, true);
290 | //diff == [
291 | // {op: "test", path: "/user/lastName", value: "Einstein"},
292 | // {op: "replace", path: "/user/lastName", value: "Collins"}
293 | // ];
294 | ```
295 |
296 | #### `jsonpatch.validate(patch: Operation[], document?: any, validator?: Function): JsonPatchError`
297 |
298 | See [Validation notes](#validation-notes)
299 |
300 | Validates a sequence of operations. If `document` parameter is provided, the sequence is additionally validated against the object tree.
301 |
302 | If there are no errors, returns undefined. If there is an errors, returns a JsonPatchError object with the following properties:
303 |
304 | - `name` String - short error code
305 | - `message` String - long human readable error message
306 | - `index` Number - index of the operation in the sequence
307 | - `operation` Object - reference to the operation
308 | - `tree` Object - reference to the tree
309 |
310 | Possible errors:
311 |
312 | Error name | Error message
313 | ------------------------------|------------
314 | SEQUENCE_NOT_AN_ARRAY | Patch sequence must be an array
315 | OPERATION_NOT_AN_OBJECT | Operation is not an object
316 | OPERATION_OP_INVALID | Operation `op` property is not one of operations defined in RFC-6902
317 | OPERATION_PATH_INVALID | Operation `path` property is not a valid string
318 | OPERATION_FROM_REQUIRED | Operation `from` property is not present (applicable in `move` and `copy` operations)
319 | OPERATION_VALUE_REQUIRED | Operation `value` property is not present, or `undefined` (applicable in `add`, `replace` and `test` operations)
320 | OPERATION_VALUE_CANNOT_CONTAIN_UNDEFINED | Operation `value` property object has at least one `undefined` value (applicable in `add`, `replace` and `test` operations)
321 | OPERATION_PATH_CANNOT_ADD | Cannot perform an `add` operation at the desired path
322 | OPERATION_PATH_UNRESOLVABLE | Cannot perform the operation at a path that does not exist
323 | OPERATION_FROM_UNRESOLVABLE | Cannot perform the operation from a path that does not exist
324 | 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
325 | OPERATION_VALUE_OUT_OF_BOUNDS | The specified index MUST NOT be greater than the number of elements in the array
326 | TEST_OPERATION_FAILED | When operation is `test` and the test fails, applies to `applyReducer`.
327 |
328 | Example:
329 |
330 | ```js
331 | var obj = {user: {firstName: "Albert"}};
332 | var patches = [{op: "replace", path: "/user/firstName", value: "Albert"}, {op: "replace", path: "/user/lastName", value: "Einstein"}];
333 | var errors = jsonpatch.validate(patches, obj);
334 | if (errors.length == 0) {
335 | //there are no errors!
336 | }
337 | else {
338 | for (var i=0; i < errors.length; i++) {
339 | if (!errors[i]) {
340 | console.log("Valid patch at index", i, patches[i]);
341 | }
342 | else {
343 | console.error("Invalid patch at index", i, errors[i], patches[i]);
344 | }
345 | }
346 | }
347 | ```
348 |
349 | ## `OperationResult` Type
350 |
351 | Functions `applyPatch` and `applyOperation` both return `OperationResult` object. This object is:
352 |
353 | ```ts
354 | {newDocument: any, test?: boolean, removed?: any}
355 | ```
356 |
357 | Where:
358 |
359 | - `newDocument`: the new state of the document after the patch/operation is applied.
360 | - `test`: if the operation was a `test` operation. This will be its result.
361 | - `removed`: contains the removed, moved, or replaced values from the document after a `remove`, `move` or `replace` operation.
362 |
363 |
364 | ## Validation Notes
365 |
366 | Functions `applyPatch`, `applyOperation`, and `validate` accept a `validate`/ `validator` parameter:
367 |
368 | - If the `validateOperation` parameter is set to `false`, validation will not occur.
369 | - If set to `true`, the patch is extensively validated before applying using jsonpatch's default validation.
370 | - If set to a `function` callback, the patch is validated using that function.
371 |
372 | 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.
373 |
374 | - `operation` The operation it self.
375 | - `index` `operation`'s index in the patch array (if application).
376 | - `tree` The object that is supposed to be patched.
377 | - `existingPath` the path `operation` points to.
378 |
379 | ## Overwriting and `move` Operation
380 |
381 | When the target of the move operation already exists, it is cached, deep cloned and returned as `removed` in `OperationResult`.
382 |
383 | ## `undefined`s (JS to JSON projection)
384 |
385 | 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`.
386 |
387 | 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)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) treats them:
388 |
389 | > 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).
390 |
391 | See the [ECMAScript spec](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-json.stringify) for details.
392 |
393 | ## Specs/tests
394 |
395 | - [Run in browser](http://starcounter-jack.github.io/JSON-Patch/test/)
396 |
397 | ## [Contributing](CONTRIBUTING.md)
398 |
399 | ## Changelog
400 |
401 | To see the list of recent changes, see [Releases](https://github.com/Starcounter-Jack/JSON-Patch/releases).
402 |
403 | ## Footprint
404 | 4 KB minified and gzipped (12 KB minified)
405 |
406 | ## Performance
407 |
408 | ##### [`add` benchmark](https://run.perf.zone/view/JSON-Patch-Add-Operation-1535541298893)
409 |
410 | 
411 |
412 | ##### [`replace` benchmark](https://run.perf.zone/view/JSON-Patch-Replace-Operation-1535540952263)
413 |
414 | 
415 |
416 | Tested on 29.08.2018. Compared libraries:
417 |
418 | - [Starcounter-Jack/JSON-Patch](https://www.npmjs.com/package/fast-json-patch) 2.0.6
419 | - [bruth/jsonpatch-js](https://www.npmjs.com/package/json-patch) 0.7.0
420 | - [dharmafly/jsonpatch.js](https://www.npmjs.com/package/jsonpatch) 3.0.1
421 | - [jiff](https://www.npmjs.com/package/jiff) 0.7.3
422 | - [RFC6902](https://www.npmjs.com/package/rfc6902) 2.4.0
423 |
424 | 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](https://perf.zone) benchmarks linked above and open an issue to include new results.
425 |
426 | ## License
427 |
428 | MIT
429 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fast-json-patch",
3 | "version": "3.0.2",
4 | "homepage": "https://github.com/Starcounter-Jack/JSON-Patch",
5 | "description": "Fast implementation of JSON-Patch (RFC-6902) with duplex (observe changes) capabilities",
6 | "main": "dist/fast-json-patch.js",
7 | "keywords": ["json", "patch", "http", "rest"],
8 | "license": "MIT",
9 | "ignore": [
10 | ".npmignore",
11 | ".gitignore",
12 | "test",
13 | ".travis.yml",
14 | "gruntfile.js",
15 | "node_modules",
16 | "package.json"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/commonjs/duplex.js:
--------------------------------------------------------------------------------
1 | Object.defineProperty(exports, "__esModule", { value: true });
2 | /*!
3 | * https://github.com/Starcounter-Jack/JSON-Patch
4 | * (c) 2017-2021 Joachim Wester
5 | * MIT license
6 | */
7 | var helpers_js_1 = require("./helpers.js");
8 | var core_js_1 = require("./core.js");
9 | var beforeDict = new WeakMap();
10 | var Mirror = /** @class */ (function () {
11 | function Mirror(obj) {
12 | this.observers = new Map();
13 | this.obj = obj;
14 | }
15 | return Mirror;
16 | }());
17 | var ObserverInfo = /** @class */ (function () {
18 | function ObserverInfo(callback, observer) {
19 | this.callback = callback;
20 | this.observer = observer;
21 | }
22 | return ObserverInfo;
23 | }());
24 | function getMirror(obj) {
25 | return beforeDict.get(obj);
26 | }
27 | function getObserverFromMirror(mirror, callback) {
28 | return mirror.observers.get(callback);
29 | }
30 | function removeObserverFromMirror(mirror, observer) {
31 | mirror.observers.delete(observer.callback);
32 | }
33 | /**
34 | * Detach an observer from an object
35 | */
36 | function unobserve(root, observer) {
37 | observer.unobserve();
38 | }
39 | exports.unobserve = unobserve;
40 | /**
41 | * Observes changes made to an object, which can then be retrieved using generate
42 | */
43 | function observe(obj, callback) {
44 | var patches = [];
45 | var observer;
46 | var mirror = getMirror(obj);
47 | if (!mirror) {
48 | mirror = new Mirror(obj);
49 | beforeDict.set(obj, mirror);
50 | }
51 | else {
52 | var observerInfo = getObserverFromMirror(mirror, callback);
53 | observer = observerInfo && observerInfo.observer;
54 | }
55 | if (observer) {
56 | return observer;
57 | }
58 | observer = {};
59 | mirror.value = helpers_js_1._deepClone(obj);
60 | if (callback) {
61 | observer.callback = callback;
62 | observer.next = null;
63 | var dirtyCheck = function () {
64 | generate(observer);
65 | };
66 | var fastCheck = function () {
67 | clearTimeout(observer.next);
68 | observer.next = setTimeout(dirtyCheck);
69 | };
70 | if (typeof window !== 'undefined') { //not Node
71 | window.addEventListener('mouseup', fastCheck);
72 | window.addEventListener('keyup', fastCheck);
73 | window.addEventListener('mousedown', fastCheck);
74 | window.addEventListener('keydown', fastCheck);
75 | window.addEventListener('change', fastCheck);
76 | }
77 | }
78 | observer.patches = patches;
79 | observer.object = obj;
80 | observer.unobserve = function () {
81 | generate(observer);
82 | clearTimeout(observer.next);
83 | removeObserverFromMirror(mirror, observer);
84 | if (typeof window !== 'undefined') {
85 | window.removeEventListener('mouseup', fastCheck);
86 | window.removeEventListener('keyup', fastCheck);
87 | window.removeEventListener('mousedown', fastCheck);
88 | window.removeEventListener('keydown', fastCheck);
89 | window.removeEventListener('change', fastCheck);
90 | }
91 | };
92 | mirror.observers.set(callback, new ObserverInfo(callback, observer));
93 | return observer;
94 | }
95 | exports.observe = observe;
96 | /**
97 | * Generate an array of patches from an observer
98 | */
99 | function generate(observer, invertible) {
100 | if (invertible === void 0) { invertible = false; }
101 | var mirror = beforeDict.get(observer.object);
102 | _generate(mirror.value, observer.object, observer.patches, "", invertible);
103 | if (observer.patches.length) {
104 | core_js_1.applyPatch(mirror.value, observer.patches);
105 | }
106 | var temp = observer.patches;
107 | if (temp.length > 0) {
108 | observer.patches = [];
109 | if (observer.callback) {
110 | observer.callback(temp);
111 | }
112 | }
113 | return temp;
114 | }
115 | exports.generate = generate;
116 | // Dirty check if obj is different from mirror, generate patches and update mirror
117 | function _generate(mirror, obj, patches, path, invertible) {
118 | if (obj === mirror) {
119 | return;
120 | }
121 | if (typeof obj.toJSON === "function") {
122 | obj = obj.toJSON();
123 | }
124 | var newKeys = helpers_js_1._objectKeys(obj);
125 | var oldKeys = helpers_js_1._objectKeys(mirror);
126 | var changed = false;
127 | var deleted = false;
128 | //if ever "move" operation is implemented here, make sure this test runs OK: "should not generate the same patch twice (move)"
129 | for (var t = oldKeys.length - 1; t >= 0; t--) {
130 | var key = oldKeys[t];
131 | var oldVal = mirror[key];
132 | if (helpers_js_1.hasOwnProperty(obj, key) && !(obj[key] === undefined && oldVal !== undefined && Array.isArray(obj) === false)) {
133 | var newVal = obj[key];
134 | if (typeof oldVal == "object" && oldVal != null && typeof newVal == "object" && newVal != null && Array.isArray(oldVal) === Array.isArray(newVal)) {
135 | _generate(oldVal, newVal, patches, path + "/" + helpers_js_1.escapePathComponent(key), invertible);
136 | }
137 | else {
138 | if (oldVal !== newVal) {
139 | changed = true;
140 | if (invertible) {
141 | patches.push({ op: "test", path: path + "/" + helpers_js_1.escapePathComponent(key), value: helpers_js_1._deepClone(oldVal) });
142 | }
143 | patches.push({ op: "replace", path: path + "/" + helpers_js_1.escapePathComponent(key), value: helpers_js_1._deepClone(newVal) });
144 | }
145 | }
146 | }
147 | else if (Array.isArray(mirror) === Array.isArray(obj)) {
148 | if (invertible) {
149 | patches.push({ op: "test", path: path + "/" + helpers_js_1.escapePathComponent(key), value: helpers_js_1._deepClone(oldVal) });
150 | }
151 | patches.push({ op: "remove", path: path + "/" + helpers_js_1.escapePathComponent(key) });
152 | deleted = true; // property has been deleted
153 | }
154 | else {
155 | if (invertible) {
156 | patches.push({ op: "test", path: path, value: mirror });
157 | }
158 | patches.push({ op: "replace", path: path, value: obj });
159 | changed = true;
160 | }
161 | }
162 | if (!deleted && newKeys.length == oldKeys.length) {
163 | return;
164 | }
165 | for (var t = 0; t < newKeys.length; t++) {
166 | var key = newKeys[t];
167 | if (!helpers_js_1.hasOwnProperty(mirror, key) && obj[key] !== undefined) {
168 | patches.push({ op: "add", path: path + "/" + helpers_js_1.escapePathComponent(key), value: helpers_js_1._deepClone(obj[key]) });
169 | }
170 | }
171 | }
172 | /**
173 | * Create an array of patches from the differences in two objects
174 | */
175 | function compare(tree1, tree2, invertible) {
176 | if (invertible === void 0) { invertible = false; }
177 | var patches = [];
178 | _generate(tree1, tree2, patches, '', invertible);
179 | return patches;
180 | }
181 | exports.compare = compare;
182 |
--------------------------------------------------------------------------------
/commonjs/helpers.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * https://github.com/Starcounter-Jack/JSON-Patch
3 | * (c) 2017-2022 Joachim Wester
4 | * MIT licensed
5 | */
6 | var __extends = (this && this.__extends) || (function () {
7 | var extendStatics = function (d, b) {
8 | extendStatics = Object.setPrototypeOf ||
9 | ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
10 | function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
11 | return extendStatics(d, b);
12 | };
13 | return function (d, b) {
14 | extendStatics(d, b);
15 | function __() { this.constructor = d; }
16 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
17 | };
18 | })();
19 | Object.defineProperty(exports, "__esModule", { value: true });
20 | var _hasOwnProperty = Object.prototype.hasOwnProperty;
21 | function hasOwnProperty(obj, key) {
22 | return _hasOwnProperty.call(obj, key);
23 | }
24 | exports.hasOwnProperty = hasOwnProperty;
25 | function _objectKeys(obj) {
26 | if (Array.isArray(obj)) {
27 | var keys_1 = new Array(obj.length);
28 | for (var k = 0; k < keys_1.length; k++) {
29 | keys_1[k] = "" + k;
30 | }
31 | return keys_1;
32 | }
33 | if (Object.keys) {
34 | return Object.keys(obj);
35 | }
36 | var keys = [];
37 | for (var i in obj) {
38 | if (hasOwnProperty(obj, i)) {
39 | keys.push(i);
40 | }
41 | }
42 | return keys;
43 | }
44 | exports._objectKeys = _objectKeys;
45 | ;
46 | /**
47 | * Deeply clone the object.
48 | * https://jsperf.com/deep-copy-vs-json-stringify-json-parse/25 (recursiveDeepCopy)
49 | * @param {any} obj value to clone
50 | * @return {any} cloned obj
51 | */
52 | function _deepClone(obj) {
53 | switch (typeof obj) {
54 | case "object":
55 | return JSON.parse(JSON.stringify(obj)); //Faster than ES5 clone - http://jsperf.com/deep-cloning-of-objects/5
56 | case "undefined":
57 | return null; //this is how JSON.stringify behaves for array items
58 | default:
59 | return obj; //no need to clone primitives
60 | }
61 | }
62 | exports._deepClone = _deepClone;
63 | //3x faster than cached /^\d+$/.test(str)
64 | function isInteger(str) {
65 | var i = 0;
66 | var len = str.length;
67 | var charCode;
68 | while (i < len) {
69 | charCode = str.charCodeAt(i);
70 | if (charCode >= 48 && charCode <= 57) {
71 | i++;
72 | continue;
73 | }
74 | return false;
75 | }
76 | return true;
77 | }
78 | exports.isInteger = isInteger;
79 | /**
80 | * Escapes a json pointer path
81 | * @param path The raw pointer
82 | * @return the Escaped path
83 | */
84 | function escapePathComponent(path) {
85 | if (path.indexOf('/') === -1 && path.indexOf('~') === -1)
86 | return path;
87 | return path.replace(/~/g, '~0').replace(/\//g, '~1');
88 | }
89 | exports.escapePathComponent = escapePathComponent;
90 | /**
91 | * Unescapes a json pointer path
92 | * @param path The escaped pointer
93 | * @return The unescaped path
94 | */
95 | function unescapePathComponent(path) {
96 | return path.replace(/~1/g, '/').replace(/~0/g, '~');
97 | }
98 | exports.unescapePathComponent = unescapePathComponent;
99 | function _getPathRecursive(root, obj) {
100 | var found;
101 | for (var key in root) {
102 | if (hasOwnProperty(root, key)) {
103 | if (root[key] === obj) {
104 | return escapePathComponent(key) + '/';
105 | }
106 | else if (typeof root[key] === 'object') {
107 | found = _getPathRecursive(root[key], obj);
108 | if (found != '') {
109 | return escapePathComponent(key) + '/' + found;
110 | }
111 | }
112 | }
113 | }
114 | return '';
115 | }
116 | exports._getPathRecursive = _getPathRecursive;
117 | function getPath(root, obj) {
118 | if (root === obj) {
119 | return '/';
120 | }
121 | var path = _getPathRecursive(root, obj);
122 | if (path === '') {
123 | throw new Error("Object not found in root");
124 | }
125 | return "/" + path;
126 | }
127 | exports.getPath = getPath;
128 | /**
129 | * Recursively checks whether an object has any undefined values inside.
130 | */
131 | function hasUndefined(obj) {
132 | if (obj === undefined) {
133 | return true;
134 | }
135 | if (obj) {
136 | if (Array.isArray(obj)) {
137 | for (var i_1 = 0, len = obj.length; i_1 < len; i_1++) {
138 | if (hasUndefined(obj[i_1])) {
139 | return true;
140 | }
141 | }
142 | }
143 | else if (typeof obj === "object") {
144 | var objKeys = _objectKeys(obj);
145 | var objKeysLength = objKeys.length;
146 | for (var i = 0; i < objKeysLength; i++) {
147 | if (hasUndefined(obj[objKeys[i]])) {
148 | return true;
149 | }
150 | }
151 | }
152 | }
153 | return false;
154 | }
155 | exports.hasUndefined = hasUndefined;
156 | function patchErrorMessageFormatter(message, args) {
157 | var messageParts = [message];
158 | for (var key in args) {
159 | var value = typeof args[key] === 'object' ? JSON.stringify(args[key], null, 2) : args[key]; // pretty print
160 | if (typeof value !== 'undefined') {
161 | messageParts.push(key + ": " + value);
162 | }
163 | }
164 | return messageParts.join('\n');
165 | }
166 | var PatchError = /** @class */ (function (_super) {
167 | __extends(PatchError, _super);
168 | function PatchError(message, name, index, operation, tree) {
169 | var _newTarget = this.constructor;
170 | var _this = _super.call(this, patchErrorMessageFormatter(message, { name: name, index: index, operation: operation, tree: tree })) || this;
171 | _this.name = name;
172 | _this.index = index;
173 | _this.operation = operation;
174 | _this.tree = tree;
175 | Object.setPrototypeOf(_this, _newTarget.prototype); // restore prototype chain, see https://stackoverflow.com/a/48342359
176 | _this.message = patchErrorMessageFormatter(message, { name: name, index: index, operation: operation, tree: tree });
177 | return _this;
178 | }
179 | return PatchError;
180 | }(Error));
181 | exports.PatchError = PatchError;
182 |
--------------------------------------------------------------------------------
/dist/fast-json-patch.min.js:
--------------------------------------------------------------------------------
1 | /*! fast-json-patch, version: 3.1.1 */
2 | var jsonpatch=function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=2)}([function(e,t){
3 | /*!
4 | * https://github.com/Starcounter-Jack/JSON-Patch
5 | * (c) 2017-2022 Joachim Wester
6 | * MIT licensed
7 | */
8 | var r,n=this&&this.__extends||(r=function(e,t){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r])})(e,t)},function(e,t){function n(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)});Object.defineProperty(t,"__esModule",{value:!0});var o=Object.prototype.hasOwnProperty;function a(e,t){return o.call(e,t)}function i(e){if(Array.isArray(e)){for(var t=new Array(e.length),r=0;r=48&&t<=57))return!1;r++}return!0},t.escapePathComponent=p,t.unescapePathComponent=function(e){return e.replace(/~1/g,"/").replace(/~0/g,"~")},t._getPathRecursive=u,t.getPath=function(e,t){if(e===t)return"/";var r=u(e,t);if(""===r)throw new Error("Object not found in root");return"/"+r},t.hasUndefined=function e(t){if(void 0===t)return!0;if(t)if(Array.isArray(t)){for(var r=0,n=t.length;r0&&"constructor"==d[y-1]))throw new TypeError("JSON-Patch: modifying `__proto__` or `constructor/prototype` prop is banned for security reasons, if this was on purpose, please set `banPrototypeModifications` flag false and pass it to this function. More info in fast-json-patch README");if(p&&void 0===_&&(void 0===v[O]?_=d.slice(0,y).join("/"):y==w-1&&(_=r.path),void 0!==_&&m(r,0,e,_)),y++,Array.isArray(v)){if("-"===O)O=v.length;else{if(p&&!n.isInteger(O))throw new t.JsonPatchError("Expected an unsigned base-10 integer value, making the new referenced value the array element with the zero-based index","OPERATION_PATH_ILLEGAL_ARRAY_INDEX",h,r,e);n.isInteger(O)&&(O=~~O)}if(y>=w){if(p&&"add"===r.op&&O>v.length)throw new t.JsonPatchError("The specified index MUST NOT be greater than the number of elements in the array","OPERATION_VALUE_OUT_OF_BOUNDS",h,r,e);if(!1===(l=a[r.op].call(r,v,O,e)).test)throw new t.JsonPatchError("Test operation failed","TEST_OPERATION_FAILED",h,r,e);return l}}else if(y>=w){if(!1===(l=o[r.op].call(r,v,O,e)).test)throw new t.JsonPatchError("Test operation failed","TEST_OPERATION_FAILED",h,r,e);return l}if(v=v[O],p&&y0)throw new t.JsonPatchError('Operation `path` property must start with "/"',"OPERATION_PATH_INVALID",r,e,a);if(("move"===e.op||"copy"===e.op)&&"string"!=typeof e.from)throw new t.JsonPatchError("Operation `from` property is not present (applicable in `move` and `copy` operations)","OPERATION_FROM_REQUIRED",r,e,a);if(("add"===e.op||"replace"===e.op||"test"===e.op)&&void 0===e.value)throw new t.JsonPatchError("Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)","OPERATION_VALUE_REQUIRED",r,e,a);if(("add"===e.op||"replace"===e.op||"test"===e.op)&&n.hasUndefined(e.value))throw new t.JsonPatchError("Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)","OPERATION_VALUE_CANNOT_CONTAIN_UNDEFINED",r,e,a);if(a)if("add"==e.op){var p=e.path.split("/").length,u=i.split("/").length;if(p!==u+1&&p!==u)throw new t.JsonPatchError("Cannot perform an `add` operation at the desired path","OPERATION_PATH_CANNOT_ADD",r,e,a)}else if("replace"===e.op||"remove"===e.op||"_get"===e.op){if(e.path!==i)throw new t.JsonPatchError("Cannot perform the operation at a path that does not exist","OPERATION_PATH_UNRESOLVABLE",r,e,a)}else if("move"===e.op||"copy"===e.op){var s=c([{op:"_get",path:e.from,value:void 0}],a);if(s&&"OPERATION_PATH_UNRESOLVABLE"===s.name)throw new t.JsonPatchError("Cannot perform the operation from a path that does not exist","OPERATION_FROM_UNRESOLVABLE",r,e,a)}}function c(e,r,o){try{if(!Array.isArray(e))throw new t.JsonPatchError("Patch sequence must be an array","SEQUENCE_NOT_AN_ARRAY");if(r)u(n._deepClone(r),n._deepClone(e),o||!0);else{o=o||s;for(var a=0;a0&&(e.patches=[],e.callback&&e.callback(n)),n}function s(e,t,r,o,a){if(t!==e){"function"==typeof t.toJSON&&(t=t.toJSON());for(var i=n._objectKeys(t),p=n._objectKeys(e),u=!1,c=p.length-1;c>=0;c--){var f=e[l=p[c]];if(!n.hasOwnProperty(t,l)||void 0===t[l]&&void 0!==f&&!1===Array.isArray(t))Array.isArray(e)===Array.isArray(t)?(a&&r.push({op:"test",path:o+"/"+n.escapePathComponent(l),value:n._deepClone(f)}),r.push({op:"remove",path:o+"/"+n.escapePathComponent(l)}),u=!0):(a&&r.push({op:"test",path:o,value:e}),r.push({op:"replace",path:o,value:t}),!0);else{var h=t[l];"object"==typeof f&&null!=f&&"object"==typeof h&&null!=h&&Array.isArray(f)===Array.isArray(h)?s(f,h,r,o+"/"+n.escapePathComponent(l),a):f!==h&&(!0,a&&r.push({op:"test",path:o+"/"+n.escapePathComponent(l),value:n._deepClone(f)}),r.push({op:"replace",path:o+"/"+n.escapePathComponent(l),value:n._deepClone(h)}))}}if(u||i.length!=p.length)for(c=0;c(root: T, observer: duplex.Observer): void;
23 | observe(obj: Object | T[], callback?: (patches: core.Operation[]) => void): duplex.Observer;
24 | generate(observer: duplex.Observer