This is an implementation of the JSON Patchwork Specification.
Applies patches from a Patchwork Object to a target document.
target
(Object or Array): The document receiving the patches.source
(Object or Array): The document source for the patch values.patches
(Array): An array of patches to apply.
target
(Object) Target configuration for patch.path
(String): The target path for the patch application.tests
(Array) An array of tests for equality or values that reduce the target of the patch application.
source
(Object) Source configuration for patch.path
(String): The source path for the patch application values.tests
(Array) An array of tests for equality or values that reduce the source of the patch application.
- [
collect
] (Boolean): Collect the values from the target and the source as opposed to replacing the target values with source values. - [
merge
] (Boolean): Merge source value with target value. - [
unique
] (Boolean): Ensure that the value for a given target is unique, e.g, all array values are unique. - [
depth
] (Number): The depth to traverse up the source object graph for a source value. The value at the given depth will be applied to the target document for the patch. Depth must be a negative number. - [
operations
] (Array) Operations to perform on all values for each patch operation a patch definition executes.
var Patchwork = require('json-patchwork');
var target = { foo: { bar: 1 } };
var source = { baz: 2 };
var patches = [{
target: { path: '/foo' },
source: { path: '/' }
merge: true
}];
Patchwork.patch(target, source, patches);
/*
Result:
{
foo: {
bar: 1,
baz: 2
}
}
*/
Registers an operator that can be configured as part of a patch operation that performs an operation against a patch value.
name
(String): The name of the operator.fn
(Function): The function that will operate on a patch. MUST return a value.operation
(Object): Contains patches for a patch operation and meta data.value
(Object, Array, or Primative): The value being modified.
var Patchwork = require('json-patchwork');
Patchwork.register('custom' function (operation, value) {
// do something interesting
return value;
});
Registers an operator that can be configured as part of a patch operation that performs an operation against a patch value.
name
(String): The name of the operator.
var Patchwork = require('json-patchwork');
Patchwork.unregister('custom');
Get a value using a patchwork compatible path.
input
(Object): Source object.path
(Array|String): Key-path to get.def
(Mixed): Default value.
(Mixed): The value for the path.
var Patchwork = require('json-patchwork');
var source = {
foo: ['bar', 'baz']
};
var val = Patchwork.get(source, '/foo/1')
/*
Result:
'baz'
*/
Set a value using a patchwork compatible path.
input
(Object): Target object.path
(Array|String): Key-path to set.value
(Mixed): Value to set.
(Object): The target object.
var Patchwork = require('json-patchwork');
var target = {
foo: ['bar', 'baz']
};
var val = Patchwork.set(target, '/foo/1', 'something')
/*
Result:
{
foo: ['bar', 'something']
}
*/
Expand a Patchwork compatible path.
subject
(Object): Source to walk through.path
(String|Array): Path to expand.strict
(Boolean): Only return valid paths.asArray
(Boolean): Return an array instead of a string.
(Array): The expanded path
var Patchwork = require('json-patchwork');
var subject = {
foo: ['bar', 'baz']
};
var paths = Patchwork.expand(subject, '/foo/@');
/*
Result:
[ '/foo/0', '/foo/1' ]
*/
Split a path; Removes falsy values and ignores escaped path delimiters
path
(String|Array): String path to split.
(Array): Path parts.
var Patchwork = require('json-patchwork');
var path = '/foo/bar/baz';
var paths = Patchwork.split(path);
/*
Result:
[ 'foo', 'bar', 'baz' ]
*/
The following operators are included with JSON Patchwork.
Used to shape patch values. Virtual patches are used to perform patches on a per shape basis.
var Patchwork = require('json-patchwork');
var source = {
foo: [{ id: 1, prop: 'here' }, { id: 2, prop: 'there' }, { id: 3, prop: 'everywhere' }]
};
var target = {};
var patches = [{
target: { path: '/bar' },
source: { path: '/foo/@' },
collect: true,
operations: [{
type: 'shape',
virtual: {
microverse: [{
target: {
path: '/microverse'
},
source: {
path: '/prop',
tests: [{
path: '/prop',
operator: '==',
value: 'everywhere'
}]
}
}]
},
shape: {
id: 'id',
b: 'prop',
c: '$microverse'
}
}]
}];
Patchwork.patch(target, source, patches);
/*
Result:
{ bar:
[ { id: 1, b: 'here', c: null },
{ id: 2, b: 'there', c: null },
{ id: 3, b: 'everywhere', c: 'everywhere' } ] }
*/
The library is based on the JSON Patchwork specification defined below.
JSON Patchwork defines a JSON document structure for expressing a sequence of value patching operations to apply from a source JSON document to a target JSON document.
Values (patches) are extracted from source documents based upon patch operations.
Values (patches) from the source document are applied to target document based upon patch operations.
A JSON Patchwork document is a JSON document that represents an array of patch operation objects. Each object represents a target to apply a source patch.
A JSON Patchwork object maps a (dynamic) target path to instructions to a (dynamic) source path for applying patches.
The target path of a patchwork object identifies the path which to apply patches from a source document.
[{
"target": {
"path": "/target/path"
}
}]
The source path of a patchwork object identifies the path from which patch values should be resolved in the source document.
[{
"target": {
"path": "/target/path"
}
"source": {
"path": "/source/path"
}
}]
Paths are used in JSON Patchwork documents to identify a location in a target or source document.
Forward slashes in JSON Patchwork paths denote traversal into an a document. For example, /foo/bar
would resolve
to { "foo": { "bar": 1 } }
returning the value of the bar
property. /foo/bar/baz/0
would resolve to
{ "foo": { "bar": { "baz": ["some value"] } } }
returning the first element in the baz
property array. All
JSON Patch paths are valid.
The @
character can be used in a path to identify an object or array in a path that should be expanded. In the case of an array,
/foo/@
would expand { "foo": [1, 2] }
to the JSON Patch paths /foo/0
and /foo/1
. In the case of an object
/foo/@
would expand { "foo": { "bar": 1, "baz": 2 } }
to the JSON Patch paths /foo/bar
and /foo/baz
.
Iterator tokens can be combined with static path parts to extract single property values as well. For example /foo/@/baz
would expand
{ "foo": { "bar": { "baz": 2 }, "foo": { "baz": 2 } } }
to the JSON Patch paths /foo/bar/baz
and /foo/foo/baz
.
Multiple iterator tokens can be used in a path to iterate into a path of objects and arrays. For example /foo/@/bar/@
would expand
{ { "foo": { "prop1": { "bar": [1, 2] }, "prop2": { "bar": [1, 2] } } }
to the JSON Patch paths /foo/prop1/0
, /foo/prop1/1
,
/foo/prop2/0
, and /foo/prop2/1
.
There are 4 different patching scenarios. If a path doesn't exist during a patch operation it will be created within the limits of the patching rules.
If both the target and source paths are static then the value from the source path is set at the target path.
var Patchwork = require('json-patchwork');
var target = {};
var source = { foo: { bar: 'baz' } };
var patches = [{
"target": {
"path": "/here"
},
"source": {
"path": "/foo"
}
}];
Patchwork.patch(target, source, patches);
/*
Result:
{
{ here: { bar: 'baz' } }
}
*/
If the target path is static and the source path is dynamic then source path will be expanded and the value at the last token will be set at the target path.
var Patchwork = require('json-patchwork');
var target = {};
var source = { foo: [1, 2, 3, 4] };
var patches = [{
"target": {
"path": "/here"
},
"source": {
"path": "/foo/@"
}
}];
Patchwork.patch(target, source, patches);
/*
Result:
{
{ here: 4 }
}
*/
If the target path is dynamic and source path is static then the value from the source path is set to the expanded target paths.
var Patchwork = require('json-patchwork');
var target = { here: [1, 2, 3, 4] };
var source = { foo: [5, 6, 7, 8] };
var patches = [{
"target": {
"path": "/here/@"
},
"source": {
"path": "/foo"
}
}];
Patchwork.patch(target, source, patches);
/*
Result:
{
{ here: [ [ 5, 6, 7, 8 ], [ 5, 6, 7, 8 ], [ 5, 6, 7, 8 ], [ 5, 6, 7, 8 ] ] }
}
*/
If both the target and source paths are dynamic then the value from the value at the last token of the source will be set at each expanded target path.
var Patchwork = require('json-patchwork');
var target = { foo: [1, 2, 3, 4] };
var source = { foo: [5, 6, 7, 8, 9] };
var patches = [{
"target": {
"path": "/foo/@"
},
"source": {
"path": "/foo/@"
}
}];
Patchwork.patch(target, source, patches);
/*
Result:
{
{ foo: [ 9, 9, 9, 9 ] }
}
*/
Patch modifiers describe how patch values should be applied in addition to the rules/scenarios defined in Paths.
If merge
is true
the source value is merged into the target value.
var Patchwork = require('json-patchwork');
var target = { foo: [1, 2] };
var source = { bar: [5, 6] };
var patches = [{
"merge": true,
"target": {
"path": "/"
},
"source": {
"path": "/"
}
}];
Patchwork.patch(target, source, patches);
/*
Result:
{
{ foo: [ 1, 2 ], bar: [ 5, 6 ] }
}
*/
If collect
is true
then values are "collected" instead of being replaced.
var Patchwork = require('json-patchwork');
var target = { foo: [1, 2] };
var source = { bar: [5, 6] };
var patches = [{
"collect": true,
"target": {
"path": "/foo"
},
"source": {
"path": "/bar"
}
}];
Patchwork.patch(target, source, patches);
/*
Result:
{
{ foo: [ 1, 2, 5, 6 ] }
}
*/
Collected objects are transformed into array like objects.
var Patchwork = require('json-patchwork');
var target = {};
var source = { foo: 1, bar: 2, baz: 3 };
var patches = [{
"collect": true,
"target": {
"path": "/"
},
"source": {
"path": "/@"
}
}];
Patchwork.patch(target, source, patches);
/*
Result:
{
{ '0': 1, '1': 2, '2': 3, length: 3 }
}
*/
The depth modifier specifies a distance to traverse up the source object graph path from the source value. This path is applied to the target path when the patch operation is applied. Depth must be a negative number.
var Patchwork = require('json-patchwork');
var target = { foo: { bar: [], baz: [] } };
var source = { foo: { bar: [ [ { here: 1 } ] ], baz: [ [ { there: 2 } ] ] } };
var patches = [{
"depth": -2,
"target": {
"path": "/foo/@"
},
"source": {
"path": "/foo/@/@/@"
}
}];
Patchwork.patch(target, source, patches);
/*
Result:
{
"foo": {
"bar": [
[
{
"there": 2
}
]
],
"baz": [
[
{
"there": 2
}
]
]
}
}
*/
The operations
property of the patchwork object contains instructions for altering patch values.
[{
"target": { "path": "/target/path" },
"source": { "path": "/source/path" }
"operations": [{}]
}]
[{
"target": { "path": "/target/path" },
"source": { "path": "/source/path" }
operations: [{
type: 'shape',
shape: {
id: 'id',
a: 'prop'
}
}]
}]
The specification implementation should pass the operation definition and the value to a function that implements the operation. The implementation should return the modified value.
An array of tests for equality or values that reduce the target or source of a patch application.
[{
"target": { "path": "/target/path", "tests": [] },
"source": { "path": "/source/path", "tests": [] }
}]
[{
"target": { "path": "/target/path" },
"source": {
"path": "/source/path",
"tests": [{
path: '/prop',
operator: '==',
value: 'everywhere'
}]
}
}]
This is not a final draft. The list is limited to the required use cases upon specification draft. Tests will likely expand and change.
- '==': equals
- '!=': not equals
- '===': strict equals
- '!==': strict not equals
- '~': regular expression
- '!~': negated regular expression
- 'in': in an array or object
- 'notIn' not in an array or object
Wrapping the above test operators in parens () will compare the same path in the source and target. Example:
{
"target": {
"path": "/path/@"
},
"source": {
"path": "/path/@",
"tests": [
{
"path": "/path/@/target",
"operator": "(==)",
"value": "/path/@/path/target/"
}
]
}
}