Skip to content

Instantly share code, notes, and snippets.

@MaxMonteil
Created August 23, 2025 12:14
Show Gist options
  • Select an option

  • Save MaxMonteil/ddde26181623d63f9acd3527e16422cf to your computer and use it in GitHub Desktop.

Select an option

Save MaxMonteil/ddde26181623d63f9acd3527e16422cf to your computer and use it in GitHub Desktop.
TS typed JSON Patch RFC 6902
/**
* Type definitions for JSON Patch, based on RFC 6902:
* https://datatracker.ietf.org/doc/html/rfc6902
*
* This file defines the TypeScript types for JSON Patch operations
* (`add`, `remove`, `replace`, `move`, `copy`, `test`) and JSON Patch
* documents. These types are intended to ensure type safety when
* constructing or working with JSON Patch structures.
*
* @see RFC 6902 (JSON Patch): https://datatracker.ietf.org/doc/html/rfc6902
*/
/** A string specifying a path in a JSON document. */
type JSONPointer = `/${string}`
/** Allowed and serializable values for a JSON document. */
type JSONValue =
| string | number | boolean | null
| JSONValue[]
| { [key: string]: JSONValue }
/** The JSON Patch operations that MUST have a value property. */
type OpsWithValue = 'test' | 'add' | 'replace'
/** The list of allowed Patch operations. */
type JSONPatchOp = OpsWithValue | 'remove' | 'move' | 'copy'
/**
* Generic accepted by JSONPatch operation types to specify:
* - an optional `meta` property
* - an optional `value` type for operations that require a value
*/
type JSONPatchGeneric<Op extends JSONPatchOp> = Op extends OpsWithValue
? { meta?: unknown, value?: JSONValue }
: { meta?: unknown }
/** Expands the properties required by {@link AbstractJSONPatchItem}, plus a `meta` type if given. */
type JSONPatchItem<T extends { meta?: unknown } = {}> =
{ path: JSONPointer } &
(T extends { meta: infer M } ? { meta: M } : {})
/**
* Extracts the `value` type from {@link JSONPatchGeneric} if given.
* Defaults to `unknown` otherwise.
*/
type JSONPatchValue<T extends { value?: unknown } = {}> = T extends { value: infer V } ? V : unknown
/**
* A JSON Patch Add operation.
* - inserts a value into an array at a given index
* - adds a new value to an object if the member does not exist
* - overwrites an object value if the member exists
*/
export type AddOp<T extends JSONPatchGeneric<'add'> = {}> = Beautify<
JSONPatchItem<T> &
{ op: 'add'; value: JSONPatchValue<T> }
>
/**
* A JSON Patch Remove operation.
* Removes the value at the target location.
*
* Will fail if the target location does not exist.
*/
export type RemoveOp<T extends JSONPatchGeneric<'remove'>> = Beautify<
JSONPatchItem<T> &
{ op: 'remove' }
>
/**
* A JSON Patch Replace operation.
* Replaces the value at a given location with a new value.
*
* Will fail if the target location does not exist.
*
* Equivalent to a {@link JSONPatchRemove} operation followed by a
* {@link JSONPatchAdd} operation at the same location with the removed value.
*/
export type ReplaceOp<T extends JSONPatchGeneric<'replace'>> = Beautify<
JSONPatchItem<T> &
{ op: 'replace'; value: JSONPatchValue<T> }
>
/**
* A JSON Patch Move operation.
* Removes the value at the given `from` location and adds it at the given
* `path` location.
*
* Will fail if the `from` location does not exist.
*
* Equivalent to a {@link JSONPatchRemove} operation at the `from` location
* followed by a {@link JSONPatchAdd} operation at the target location with
* the removed value.
*/
export type MoveOp<T extends JSONPatchGeneric<'move'>> = Beautify<
JSONPatchItem<T> &
{ op: 'move'; from: JSONPointer }
>
/**
* A JSON Patch Copy operation.
* Copies the value at the given `from` location and adds it at the given
* `path` location.
*
* Will fail if the `from` location does not exist.
*
* Equivalent to a {@link JSONPatchAdd} operation at the target location
* with the value at the given `from` location.
*/
export type CopyOp<T extends JSONPatchGeneric<'copy'>> = Beautify<
JSONPatchItem<T> &
{ op: 'copy'; from: JSONPointer }
>
/**
* A JSON Patch Test operation.
* Tests that the value at the given location is equal to the given value.
*
* Will fail if the value at the given location is not equal
* to the given value.
*
* - `strings`: are considered equal if they contain the same number of
* Unicode characters and their code points are byte-by-byte equal.
*
* - `numbers`: are considered equal if their values are numerically equal.
*
* - `arrays`: are considered equal if they contain the same number of
* values, and if each value can be considered equal to the value at
* the corresponding position in the other array, using this list of
* type-specific rules.
*
* - `objects`: are considered equal if they contain the same number of
* members, and if each member can be considered equal to a member in
* the other object, by comparing their keys (as strings) and their
* values (using this list of type-specific rules).
*
* - `literals` (`false`, `true`, and `null`): are considered equal if they are
* the same.
*/
export type TestOp<T extends JSONPatchGeneric<'test'> = {}> = Beautify<
JSONPatchItem<T> &
{ op: 'test'; value: JSONPatchValue<T> }
>
/**
* A JSON Patch document.
* Represents an array of Path operations to apply to a target JSON document.
*
* An optional `Meta` type can be given to specify the type of an optional
* `meta` property in each operation.
*
* This field is ignored when validating or applying the patch document.
*
* To use a stricter version that disallows `meta` see {@link StrictJSONPatch}.
*
* List of Patch operations:
* - {@link AddOp}
* - {@link RemoveOp}
* - {@link ReplaceOp}
* - {@link MoveOp}
* - {@link CopyOp}
* - {@link TestOp}
*/
export type JSONPatch<Meta = {}> = Array<
| AddOp<{ meta: Meta }>
| RemoveOp<{ meta: Meta }>
| ReplaceOp<{ meta: Meta }>
| MoveOp<{ meta: Meta }>
| CopyOp<{ meta: Meta }>
| TestOp<{ meta: Meta }>
>
/**
* A JSON Patch document.
* This more strictly matches RFC 6902 and prevents using and setting `meta`.
*
* To use a `meta` field, see {@link JSONPatch}.
*/
export type StrictJSONPatch = JSONPatch<never>
/* UTILITY TYPES */
// The intersection here is to maintain optional properties as optional
/** Cleans up the inferred type on hover. */
type Prettify<T> = {
[K in keyof T as object extends Pick<T, K> ? K : never]?: T[K]
} & {
[K in keyof T as object extends Pick<T, K> ? never : K]: T[K]
}
/** Sometimes, being Pretty isn't enough. This is useful to normalize object intersections. */
type Beautify<T> = T extends object ? { [P in keyof T]: T[P] } : Prettify<T>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment