npm install @ferrow/json-patch-tsRFC 6902 JSON Patch and RFC 6901 JSON Pointer: apply, generate, and validate patches with zero dependencies.
Strict TypeScript, zero runtime dependencies, immutable operations (structural sharing — untouched branches of the document are reused, not copied), and a runnable demo covering the RFC 6902 Appendix A examples plus pointer escaping and round-trip diffing.
This is a standalone repo, not (yet) published to npm. Clone it and build, or
copy src/ into your project:
git clone https://github.com/FerrowAI/json-patch-ts.git
cd json-patch-ts
npm install
npm run build # emits dist/ (CommonJS + .d.ts)
npm run demo # runs examples/demo.js against dist/import{apply,generate,validatePatch,get}from"json-patch-ts";constdoc={name: "Ada",tags: ["math"]};// Apply a patch (RFC 6902). Returns a NEW document; `doc` is never mutated.constpatched=apply(doc,[{op: "add",path: "/tags/-",value: "computing"},{op: "replace",path: "/name",value: "Ada Lovelace"},]);// patched = { name: "Ada Lovelace", tags: ["math", "computing"] }// Generate a patch between two documents.constpatch=generate(doc,patched);// apply(doc, patch) deep-equals patched// Validate patch structure before applying it.constresult=validatePatch(patch);if(!result.valid){console.error(result.errors);// [{ index, reason }, ...]}// Read a value by JSON Pointer (RFC 6901).get(patched,"/tags/0");// "math"parsePointer(pointer: string): string[]— split a pointer into unescaped reference tokens ("" -> []).compilePointer(tokens: string[]): string— join raw tokens into an escaped pointer string.escapeToken(token: string): string/unescapeToken(token: string): string— apply/reverse the~0/~1escaping rules for a single token.get(doc: unknown, pointer: string): unknown— resolve a pointer; throwsJsonPointerErrorif it doesn't resolve.exists(doc: unknown, pointer: string): boolean— likeget, but returnsfalseinstead of throwing.set(doc: unknown, pointer: string, value: unknown, mode?: "insert" | "overwrite"): unknown— return a new document withvaluewritten atpointer, without mutatingdoc.modecontrols array target behavior:"insert"(default) shifts elements right (JSON Patchaddsemantics),"overwrite"replaces the element in place (JSON Patchreplacesemantics). Supports the array"-"append token.remove(doc: unknown, pointer: string): unknown— return a new document with the value atpointerremoved (array elements are spliced out).
apply<T>(doc: T, patch: JsonPatchOp[]): T— apply a patch, returning a new document. ThrowsJsonPatchError(with.indexand.op) on the first failing operation; the patch is applied atomically — if any op fails (including atestmismatch), no partial result escapes anddocitself was never mutated.tryApply<T>(doc: T, patch: JsonPatchOp[]): { ok: true; doc: T } | { ok: false; error: JsonPatchError }— same asapply, without throwing.- Supported ops:
add,remove,replace,move,copy,test— full RFC 6902 semantics, including array"-"append,move-into-own-child rejection, and atomic abort on a failingtest. JsonPatchError—{ message, index, op }.
generate(a: unknown, b: unknown): JsonPatchOp[]— produce a patch such thatapply(a, generate(a, b))deep-equalsb. See Limits below.
validatePatch(patch: unknown): { valid: true; errors: [] } | { valid: false; errors: { index: number; reason: string }[] }— structural validation only (well-formed ops, required members present, valid pointers). It does not check a patch against a target document — a structurally valid patch can still fail toapply(e.g. areplaceon a path that doesn't exist).
generate()is index-based, not a true LCS/diff, for arrays. Elements are compared position-by-position. Appending or removing from the end of an array produces minimaladd/removeops; an insertion or deletion in the middle will generally produce areplacefor every shifted element instead of a singleadd/remove. The output is always correct (apply(a, generate(a, b))deep-equalsb) but is not guaranteed minimal. If you need a minimal middle-of-array diff, diff with an LCS algorithm yourself and hand the result toapplydirectly —generate()'s contract is correctness, not minimality.deepEqual(used internally bygenerateand thetestop) treatsundefinedobject values and missing keys as different from each other, matches JSON semantics (no support forDate,Map,Set, etc. — this library operates on plain JSON-compatible values only).- No streaming / no support for JSON Patch's optional
application/json-patch+jsoncontent-type framing — this is a pure data-structure library, not an HTTP layer.
npm run build && npm run demoexamples/demo.js is a runnable smoke test (not a test-framework suite) that
asserts RFC 6902 Appendix A cases, pointer escaping, atomic-abort-on-test-failure,
move-into-own-child rejection, and generate() round-trips, exiting non-zero on
any assertion failure.
MIT
Part of the ferrow-toolkit collection · Sponsored by Ferrow