simple-object-patcher
v0.0.1
Published
Simple RFC6902 object patcher, with options
Maintainers
Readme
simple-object-patcher
RFC6902 (JSON Patch) apply and diff, with the options a live-state application tends to need:
replacing the document at its root, patching destructively or into a clone, creating the parents an
add needs, and an error that says which part of the patch failed and whether anything was written
before it did.
It is a thin layer over rfc6902: that package does the patching, this one wraps it.
Installation
npm install simple-object-patcherESM only, no build step.
Usage
import { applyPatch, createPatch, PatchError } from "simple-object-patcher";
const state = { headline: "old" };
applyPatch(state, [{ op: "replace", path: "/headline", value: "new" }]);
// state is now { headline: "new" }
createPatch({ a: 1 }, { a: 2 });
// [{ op: "replace", path: "/a", value: 2 }]applyPatch(document, patch, options?)
Applies patch (an array of RFC6902 operations) and returns the patched document. By default it
mutates document in place and returns that same object.
| Option | Default | Effect |
| --- | --- | --- |
| nest | true | Allows the patch to replace the document at its root, with path: "" |
| immutable | false | Clones the document and patches the clone, leaving the original alone |
| create | true | Creates the missing parent objects an add needs |
Parts are applied one at a time, in order, and the first failure throws. Parts before the failure have already been applied; parts after it are not attempted.
nest
RFC6902 cannot replace a document at its root, because there is no parent to assign to. With nest
the document is put inside a wrapper, the patch paths are rewritten against it, and the wrapper's
contents are returned — so a whole-document replace works:
applyPatch({ a: 1 }, [{ op: "replace", path: "", value: { b: 2 } }]);
// { b: 2 }A root replace necessarily returns a different object than the one passed in, since it is a new
value rather than a mutation. Every other operation still mutates in place and returns the original
reference. Set nest: false to patch the document directly and reject root-level operations.
immutable
const before = { a: 1 };
const after = applyPatch(before, [{ op: "add", path: "/b", value: 2 }], { immutable: true });
// before is still { a: 1 }, after is { a: 1, b: 2 }A null or undefined document is treated as {}, so a state that does not exist yet can be
patched into being rather than throwing:
applyPatch(undefined, [{ op: "add", path: "/a", value: 1 }], { immutable: true });
// { a: 1 }create
RFC6902 requires the parent of an add to exist. With create, the parents are built first, so a
nested value can be set without walking the object into place by hand:
applyPatch({}, [{ op: "add", path: "/a/b/c", value: 1 }]);
// { a: { b: { c: 1 } } }
applyPatch({}, [{ op: "add", path: "/a/b/c", value: 1 }], { create: false });
// throws PatchErrorcreatePatch(document1, document2)
Returns the patch that turns document1 into document2, or [] if they are already equal. This
is rfc6902's createPatch unchanged, re-exported so
callers do not need to depend on both packages.
const patch = createPatch(before, after);
applyPatch(before, patch, { immutable: true }); // deep-equals afterPatchError
Thrown when a part of the patch cannot be applied. Beyond message, it carries:
patch— the single operation that failed. Withneston (the default) itspathis the rewritten one, prefixed with the wrapper key.mutated— whether a non-testoperation had been attempted before the throw, meaning the document may already have been changed. A patch that fails on a leadingtestreportsfalse.
try {
applyPatch(state, patch);
}
catch(error) {
if(error instanceof PatchError && error.mutated)
// the document is in a partly-patched state; reload it rather than trusting it
}mutated is deliberately pessimistic: it reports that a write was attempted, not that it
succeeded.
Tests
npm testLicense
MIT
