# `json_patch`

The `json_patch` library provides predicates for applying JSON Patch documents as specified by RFC 6902:

- https://www.rfc-editor.org/rfc/rfc6902

Patch documents are represented as lists of JSON operation objects using the repository native JSON term representation.

## API documentation

Open the ../../apis/library_index.html#json_patch link in a web browser.

## Loading

To load all entities in this library from the repository checkout, load the `loader.lgt` file from this directory:

    | ?- logtalk_load(json_patch(loader)).

## Testing

To test this library predicates, load the `tester.lgt` file:

    | ?- logtalk_load(json_patch(tester)).

## Representation

Patch documents are JSON arrays represented as Prolog lists. Each element in the list is a JSON object representing a patch operation. The public predicate provided by this library is:

    | ?- json_patch::apply(Patch, OldJSON, NewJSON).

where `Patch` is a list of operation objects, `OldJSON` is the input JSON term, and `NewJSON` is the patched JSON term.

## Patch document structure

Each patch operation is encoded as a JSON object using any of the JSON object representations supported by the repository JSON libraries:

- Curly objects using dash pairs, e.g. `{op-add, path-'/foo', value-1}`

- List objects using `json([...])`, e.g. `json([op=add, path='/foo', value=1])`

- List objects using colon pairs, e.g. `json([':'(op, add), ':'(path, '/foo')])`

The operation member names are always:

- `op`

- `path`

- `from`

- `value`

The `path` and `from` members are JSON Pointer strings parsed using the `json_pointer` library. They can be represented as:

- atoms, e.g. `'/items/0'`

- `chars(List)`, e.g. `chars(['/', i, t, e, m, s, '/', '0'])`

- `codes(List)`, e.g. `codes([0'/, 0'i, 0't, 0'e, 0'm, 0's, 0'/, 0'0])`

For example:

    | ?- Patch = [{op-add, path-'/baz', value-1}],
         json_patch::apply(Patch, {foo-bar}, JSON).
    Patch = [{op-add,path-'/baz',value-1}]
    JSON = {foo-bar, baz-1}
    yes

## Valid operations

The library accepts the six RFC 6902 operations:

- `add` Required members: `op`, `path`, `value` Adds a value at the target path. When the target path is empty (`''`), the whole document is replaced by the given value.

      | ?- Patch = [{op-add, path-'/items/-', value-b}],
           json_patch::apply(Patch, {items-[a]}, JSON).
      Patch = [{op-add,path-'/items/-',value-b}]
      JSON = {items-[a,b]}
      yes

- `remove` Required members: `op`, `path` Removes the value at the target path. The target must exist. Removing the root document is not supported, so an empty path fails.

      | ?- Patch = [{op-remove, path-'/foo'}],
           json_patch::apply(Patch, {foo-bar}, JSON).
      Patch = [{op-remove,path-'/foo'}]
      JSON = {}
      yes

- `replace` Required members: `op`, `path`, `value` Replaces the existing value at the target path. When the target path is empty, the whole document is replaced.

      | ?- Patch = [{op-replace, path-'', value-[1,2]}],
           json_patch::apply(Patch, {foo-bar}, JSON).
      Patch = [{op-replace,path-'',value-[1,2]}]
      JSON = [1,2]
      yes

- `move` Required members: `op`, `from`, `path` Reads the value identified by `from`, removes it from the source location, and then adds it at `path`.

      | ?- Patch = [{op-move, from-'/foo', path-'/baz'}],
           json_patch::apply(Patch, {foo-bar}, JSON).
      Patch = [{op-move,from-'/foo',path-'/baz'}]
      JSON = {baz-bar}
      yes

- `copy` Required members: `op`, `from`, `path` Reads the value identified by `from` and adds a copy of it at `path`.

      | ?- Patch = [{op-copy, from-'/foo', path-'/baz'}],
           json_patch::apply(Patch, {foo-bar}, JSON).
      Patch = [{op-copy,from-'/foo',path-'/baz'}]
      JSON = {foo-bar,baz-bar}
      yes

- `test` Required members: `op`, `path`, `value` Succeeds only if the value at the target path is structurally equal to the given `value`. On success, `NewJSON` is unified with the original document.

      | ?- Patch = [{op-test, path-'/flag', value- @true}],
           json_patch::apply(Patch, {flag- @true}, JSON).
      Patch = [{op-test,path-'/flag',value- @true}]
      JSON = {flag- @true}
      yes

## Path rules

- Paths use RFC 6901 JSON Pointer syntax.

- The empty path (`''`) addresses the whole JSON document.

- Array insertion with `add` supports the special `'-'` token for appending to the end of an array.

- Array indices must be decimal integers without leading zeros, except for the single digit `0`.

- Intermediate containers must already exist. Operations do not create missing parent objects or arrays.

## Representation notes

This implementation reuses `json_pointer` for parsing patch paths and preserves the object representation found in the patched JSON term:

- curly objects stay curly objects

- `json([...])` objects stay `json([...])` objects

- key text representation such as atoms, `chars(List)`, or `codes(List)` is preserved when new members are added to an existing object

The implementation also accepts patch operation objects encoded using curly or `json([...])` object notation and pointer text represented as atoms, `chars(List)`, or `codes(List)`.

## Failure and errors

- `apply/3` throws an `instantiation_error` when `Patch` or `OldJSON` is a variable.

- `apply/3` throws `type_error(list, Patch)` when the patch document is not a list.

- `apply/3` throws `domain_error(json_patch_operation, Operation)` when an operation object is malformed.

- Operations fail when a required target value does not exist or when a `test` comparison does not succeed.

Empty patch paths are supported for root replacement, copying to the root, moving to the root, and testing the full document.