# JSON Patch and JSON Pointer

The `JsonPatch` and `JsonPointer` modules are two small, tightly-related core
utilities. `JsonPatch` computes and applies structural diffs between JSON
documents (a deterministic subset of [RFC 6902](https://www.rfc-editor.org/rfc/rfc6902)),
and `JsonPointer` handles escaping/unescaping of the path segments
([RFC 6901](https://www.rfc-editor.org/rfc/rfc6901)) that `JsonPatch` uses to
address locations inside a document. Both are plain `effect` imports:

```ts
import { JsonPatch, JsonPointer } from "effect"
```

## JsonPatch

A `JsonPatch` is an ordered list of operations applied left-to-right: each
operation observes the document produced by the operations before it. Locations
are addressed by JSON Pointer paths, where the empty path `""` targets the whole
document and `/users/0/name` targets a nested property or array element.

The common workflow is to compute a diff between a `before` and `after` value
with `get`, then replay it onto the original document with `apply`.

```ts
import { JsonPatch } from "effect"

const before = { title: "draft", tags: ["fp"] }
const after = { title: "published", tags: ["fp", "effect"] }

// Compute the structural difference between the two values.
const patch = JsonPatch.get(before, after)
// => [
// =>   { op: "add", path: "/tags/1", value: "effect" },
// =>   { op: "replace", path: "/title", value: "published" }
// => ]

// Replay it onto the original document; `before` is never mutated.
const updated = JsonPatch.apply(patch, before)
// => { title: "published", tags: ["fp", "effect"] }
```

### Mental model

- A patch is applied from first to last. Later operations see the document state
  produced by earlier ones.
- Paths are JSON Pointers. `""` is the root document; non-empty paths must start
  with `/` and are split into reference tokens.
- This module implements only the **deterministic** `add`, `remove`, and
  `replace` subset of RFC 6902. There is no `test`, `move`, or `copy`.
- `get` compares JSON **structure**, not domain meaning. It detects that a value
  changed, but does not infer semantic edits such as array moves.
- `apply` never mutates its input. It copies only the containers it changes and
  returns a new value. An empty patch returns the original document reference.
**Gotchas:** - Generated patches are deterministic but **not guaranteed to be minimal**.
- Object keys are diffed in **sorted** order, for stable output.
- Array removals are emitted **highest-index-first**, so earlier removals don't
  shift the targets of later ones.
- `"-"` is valid only as the **final** token of an `add` path (array append). It
  is rejected for `remove`/`replace`.
- Invalid paths, missing properties, and out-of-bounds array indices **throw**.
- Root `add` and `replace` (`path: ""`) replace the whole document; root
  `remove` is **unsupported** and throws.

## JsonPointer

A JSON Pointer is a sequence of reference tokens separated by `/`. The
`JsonPointer` module operates on a **single token**, not a whole pointer — a
full pointer like `/foo/bar` must be split into its tokens (`["foo", "bar"]`)
first. The two characters that need escaping inside a token are `~` and `/`:

- Escaping: `~` → `~0`, then `/` → `~1` (the `~` replacement must happen first).
- Unescaping reverses it: `~1` → `/`, then `~0` → `~`.

Empty strings are valid tokens and pass through unchanged. These functions do
not validate JSON Pointer syntax; they only handle token-level escaping.

### escapeToken

`escapeToken(token): string` encodes the special characters in a single
reference token so it is safe to use as a `/`-separated segment. Tokens with no
special characters are returned unchanged.

```ts
import { JsonPointer } from "effect"

JsonPointer.escapeToken("a/b")          // => "a~1b"
JsonPointer.escapeToken("c~d")          // => "c~0d"
JsonPointer.escapeToken("path/to~key")  // => "path~1to~0key"
JsonPointer.escapeToken("plain")        // => "plain"
```

### unescapeToken

`unescapeToken(token): string` is the inverse of `escapeToken`, decoding escaped
sequences back to their original characters.

```ts
import { JsonPointer } from "effect"

JsonPointer.unescapeToken("a~1b")          // => "a/b"
JsonPointer.unescapeToken("c~0d")          // => "c~d"
JsonPointer.unescapeToken("path~1to~0key") // => "path/to~key"
JsonPointer.unescapeToken("plain")         // => "plain"
```

### Building and parsing a full pointer

Because these functions work per-token, you map over the segments to build a
pointer and split-then-map to parse one back. This is the same idiom `JsonPatch`
uses internally to construct paths.

```ts
import { JsonPointer } from "effect"

// Build a JSON Pointer from path segments (note the special chars).
const segments = ["users", "name/alias", "value"]
const pointer = "/" + segments.map(JsonPointer.escapeToken).join("/")
// => "/users/name~1alias/value"

// Parse a JSON Pointer back into its original segments.
const tokens = pointer.split("/").slice(1).map(JsonPointer.unescapeToken)
// => ["users", "name/alias", "value"]
```
**Tip:** JSON Pointer also underpins `$ref` resolution in the
[JSON Schema](https://effect.plants.sh/schema/json-schema/) module, which uses the same escaping rules
when generating and resolving references.

## Reference

### JsonPatchOperation

A single operation in a patch document. It is a discriminated union over the
`op` field, with three deterministic variants. Paths are JSON Pointers, `value`
is a [`Schema.Json`](https://effect.plants.sh/schema/primitives/) value, and the optional `description`
field is free-form documentation.

```ts
import { JsonPatch } from "effect"

// add — insert a value. For arrays the last token may be "-" to append.
const addOp: JsonPatch.JsonPatchOperation = {
  op: "add",
  path: "/users/-",
  value: { id: 1, name: "Alice" }
}

// remove — delete the value at the location.
const removeOp: JsonPatch.JsonPatchOperation = {
  op: "remove",
  path: "/users/0"
}

// replace — overwrite the value at the location. Use "" for the root.
const replaceOp: JsonPatch.JsonPatchOperation = {
  op: "replace",
  path: "/users/0/name",
  value: "Bob",
  description: "rename the first user"
}
```

### JsonPatch

A complete patch document: `ReadonlyArray<JsonPatchOperation>`. Operations run
sequentially, so later ones observe the state produced by earlier ones. An empty
array is a no-op that returns the original document.

```ts
import { JsonPatch } from "effect"

const patch: JsonPatch.JsonPatch = [
  { op: "add", path: "/items/-", value: "apple" },
  { op: "replace", path: "/count", value: 5 },
  { op: "remove", path: "/oldField" }
]

const result = JsonPatch.apply(patch, {
  items: ["pear"],
  count: 3,
  oldField: "value"
})
// => { items: ["pear", "apple"], count: 5 }
```

### get

`get(oldValue, newValue): JsonPatch` computes a structural diff: the patch that
transforms `oldValue` into `newValue`. It returns an empty array for identical
values, recurses into nested objects and arrays, and emits a root `replace` when
the top-level values are different primitives.

Objects: keys are processed in **sorted** order, producing `add` for new keys,
`remove` for dropped keys, and recursive diffs for shared keys.

```ts
import { JsonPatch } from "effect"

const oldValue = { count: 1, name: "Alice" }
const newValue = { active: true, count: 2 }

const patch = JsonPatch.get(oldValue, newValue)
// => [
// =>   { op: "add", path: "/active", value: true },     // new key
// =>   { op: "replace", path: "/count", value: 2 },     // changed key
// =>   { op: "remove", path: "/name" }                  // dropped key
// => ]
```

Arrays are compared by index. The shared prefix is diffed positionally, trailing
removals are emitted **highest-index-first**, and trailing additions follow.

```ts
import { JsonPatch } from "effect"

const oldValue = { users: [{ id: 1, name: "Alice" }], count: 1 }
const newValue = {
  users: [{ id: 1, name: "Bob" }, { id: 2, name: "Charlie" }],
  count: 2
}

const patch = JsonPatch.get(oldValue, newValue)
// => [
// =>   { op: "replace", path: "/users/0/name", value: "Bob" },
// =>   { op: "add", path: "/users/1", value: { id: 2, name: "Charlie" } },
// =>   { op: "replace", path: "/count", value: 2 }
// => ]
```

### apply

`apply(patch, oldValue): Schema.Json` runs each operation in order and returns
the resulting document. It never mutates the input — affected arrays and objects
are copied. An empty patch returns the original reference, and a root replace
(`path: ""`) returns the provided value directly.

```ts
import { JsonPatch } from "effect"

// Apply a generated patch (round-trips a diff).
const before = { items: [1, 2, 3], total: 6 }
const after = { items: [1, 2, 3, 4], total: 10 }
JsonPatch.apply(JsonPatch.get(before, after), before)
// => { items: [1, 2, 3, 4], total: 10 }

// Apply a hand-written patch.
const document = { items: [1, 2, 3], total: 6 }
const patch: JsonPatch.JsonPatch = [
  { op: "add", path: "/items/-", value: 4 },
  { op: "replace", path: "/total", value: 10 }
]
JsonPatch.apply(patch, document)
// => { items: [1, 2, 3, 4], total: 10 }
```

Out-of-bounds indices, missing properties, and unsupported root operations throw:

```ts
import { JsonPatch } from "effect"

JsonPatch.apply([{ op: "remove", path: "/missing" }], { a: 1 })
// => throws: Property "missing" does not exist at "/missing".
```