From 4dd2a015af84e30b928e6e1a96a323e5709a0556 Mon Sep 17 00:00:00 2001 From: James Prior Date: Fri, 22 Sep 2023 18:02:33 +0100 Subject: [PATCH] docs: JSON Patch guide --- docs/docs/guides/json-patch.md | 169 +++++++++++++++++++++++++++- docs/docs/guides/jsonpath-syntax.md | 2 +- src/patch/patch.ts | 9 ++ 3 files changed, 178 insertions(+), 2 deletions(-) diff --git a/docs/docs/guides/json-patch.md b/docs/docs/guides/json-patch.md index e29517c..5a4b1b1 100644 --- a/docs/docs/guides/json-patch.md +++ b/docs/docs/guides/json-patch.md @@ -1,3 +1,170 @@ # JSON Patch -TODO: +[JSON Patch](./quick-start.md#json-patch) ([RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902)) is a standard for describing update operations to perform on JSON-like data. Each operation includes, at least, an `op` string and a `path`, which is a [JSON Pointer](./json-pointer.md). + +Use [`jsonpatch.apply(ops, data)`](../api/namespaces/jsonpatch.md#apply) to apply _ops_ to _data_, where _ops_ should be an array of [`OpObject`s](../api/namespaces/jsonpatch.md#opobject), as per RFC 6902. Patch operation are applied sequentially and, unless the target JSON document's root value is replaced, **data is modified in place**. + +```javascript +import { jsonpatch } from "json-p3"; + +const ops = [ + { op: "add", path: "/some/foo", value: { foo: {} } }, + { op: "add", path: "/some/foo", value: { bar: [] } }, + { op: "copy", from: "/some/other", path: "/some/foo/else" }, + { op: "add", path: "/some/foo/bar/-", value: 1 }, +]; + +const data = { some: { other: "thing" } }; +jsonpatch.apply(ops, data); +console.log(data); +// { some: { other: 'thing', foo: { bar: [Array], else: 'thing' } } } +``` + +Use the [`JSONPatch`](../api/classes/jsonpatch.JSONPatch.md) constructor to create a patch for repeated application. + +```javascript +import { JSONPatch } from "json-p3"; + +const patch = new JSONPatch([ + { op: "add", path: "/some/foo", value: { foo: {} } }, + { op: "add", path: "/some/foo", value: { bar: [] } }, + { op: "copy", from: "/some/other", path: "/some/foo/else" }, + { op: "add", path: "/some/foo/bar/-", value: 1 }, +]); + +const data = { some: { other: "thing" } }; +patch.apply(data); +console.log(data); +// { some: { other: 'thing', foo: { bar: [Array], else: 'thing' } } } +``` + +## Builder API + +[`JSONPatch`](../api/classes/jsonpatch.JSONPatch.md) implements a builder interface for constructing JSON Patch documents. Each of the following methods appends an operation to the patch and returns the patch instance, so method calls can be chained. + +### `add()` + +[`JSONPatch.add(pointer, value)`](../api/classes/jsonpatch.JSONPatch.md#add) appends an [_add_](https://datatracker.ietf.org/doc/html/rfc6902#section-4.1) operation to the patch. _pointer_ can be a string following RFC 6901 or an instance of [`JSONPointer`](../api/classes/jsonpointer.JSONPointer.md). + +```javascript +import { JSONPatch } from "json-p3"; + +const patch = new JSONPatch().add("/some/foo", { foo: [] }); +console.log(JSON.stringify(patch.toArray(), undefined, " ")); +``` + +```json title="output" +[ + { + "op": "add", + "path": "/some/foo", + "value": { + "foo": [] + } + } +] +``` + +### `remove()` + +[`JSONPatch.remove(pointer)`](../api/classes/jsonpatch.JSONPatch.md#add) appends an [_remove_](https://datatracker.ietf.org/doc/html/rfc6902#section-4.2) operation to the patch. _pointer_ can be a string following RFC 6901 or an instance of [`JSONPointer`](../api/classes/jsonpointer.JSONPointer.md). + +```javascript +import { JSONPatch } from "json-p3"; + +const patch = new JSONPatch().remove("/some/foo"); +console.log(JSON.stringify(patch.toArray(), undefined, " ")); +``` + +```json title="output" +[ + { + "op": "remove", + "path": "/some/foo" + } +] +``` + +### `replace()` + +[`JSONPatch.replace(pointer, value)`](../api/classes/jsonpatch.JSONPatch.md#add) appends an [_replace_](https://datatracker.ietf.org/doc/html/rfc6902#section-4.3) operation to the patch. _pointer_ can be a string following RFC 6901 or an instance of [`JSONPointer`](../api/classes/jsonpointer.JSONPointer.md). + +```javascript +import { JSONPatch } from "json-p3"; + +const patch = new JSONPatch().replace("/some/foo", [1, 2, 3]); +console.log(JSON.stringify(patch.toArray(), undefined, " ")); +``` + +```json title="output" +[ + { + "op": "replace", + "path": "/some/foo", + "value": [1, 2, 3] + } +] +``` + +### `move()` + +[`JSONPatch.move(fromPointer, toPointer)`](../api/classes/jsonpatch.JSONPatch.md#add) appends an [_move_](https://datatracker.ietf.org/doc/html/rfc6902#section-4.4) operation to the patch. _fromPointer_ and _toPointer_ can be a string following RFC 6901 or an instance of [`JSONPointer`](../api/classes/jsonpointer.JSONPointer.md). + +```javascript +import { JSONPatch } from "json-p3"; + +const patch = new JSONPatch().move("/some/foo", "/other/bar"); +console.log(JSON.stringify(patch.toArray(), undefined, " ")); +``` + +```json title="output" +[ + { + "op": "move", + "from": "/some/foo", + "path": "/other/bar" + } +] +``` + +### `copy()` + +[`JSONPatch.copy(fromPointer, toPointer)`](../api/classes/jsonpatch.JSONPatch.md#add) appends an [_copy_](https://datatracker.ietf.org/doc/html/rfc6902#section-4.5) operation to the patch. _fromPointer_ and _toPointer_ can be a string following RFC 6901 or an instance of [`JSONPointer`](../api/classes/jsonpointer.JSONPointer.md). + +```javascript +import { JSONPatch } from "json-p3"; + +const patch = new JSONPatch().copy("/some/foo", "/other/bar"); +console.log(JSON.stringify(patch.toArray(), undefined, " ")); +``` + +```json title="output" +[ + { + "op": "copy", + "from": "/some/foo", + "path": "/other/bar" + } +] +``` + +### `test()` + +[`JSONPatch.copy(pointer, value)`](../api/classes/jsonpatch.JSONPatch.md#add) appends an [_test_](https://datatracker.ietf.org/doc/html/rfc6902#section-4.6) operation to the patch. _pointer_ can be a string following RFC 6901 or an instance of [`JSONPointer`](../api/classes/jsonpointer.JSONPointer.md). + +```javascript +import { JSONPatch } from "json-p3"; + +const patch = new JSONPatch().test("/some/foo", "hello"); +console.log(JSON.stringify(patch.toArray(), undefined, " ")); +``` + +```json title="output" +[ + { + "op": "test", + "path": "/some/foo", + "value": "hello" + } +] +``` diff --git a/docs/docs/guides/jsonpath-syntax.md b/docs/docs/guides/jsonpath-syntax.md index 22a7df5..cd630eb 100644 --- a/docs/docs/guides/jsonpath-syntax.md +++ b/docs/docs/guides/jsonpath-syntax.md @@ -32,7 +32,7 @@ We use the terms "target JSON document", "target document" and "query argument" `$` is the root node identifier, pointing to the first node in the target JSON document. -```text title="Example query" +```text $.users ``` diff --git a/src/patch/patch.ts b/src/patch/patch.ts index 40287ef..fc4afda 100644 --- a/src/patch/patch.ts +++ b/src/patch/patch.ts @@ -364,6 +364,15 @@ export class JSONPatch { } } + /** + * @returns an iterator over ops in this patch. + */ + *[Symbol.iterator](): Iterator { + for (const op of this.ops) { + yield op.toObject(); + } + } + /** * * @param path -