Skip to content
This repository was archived by the owner on Mar 6, 2018. It is now read-only.

Commit 2e1a74d

Browse files
committed
Merge pull request #15 from JSON8/buildPatchFromRevert
buildPatchFromRevert
2 parents b5e20a1 + 56c8318 commit 2e1a74d

File tree

7 files changed

+214
-160
lines changed

7 files changed

+214
-160
lines changed

README.md

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ JSON8 Patch passes the entire [json-patch-tests](https://github.com/json-patch/j
1414
* [apply](#apply)
1515
* [patch](#patch)
1616
* [revert](#revert)
17+
* [buildPatchFromRevert](#buildPatchFromRevert)
1718
* [diff](#diff)
1819
* [valid](#valid)
1920
* [concat](#concat)
@@ -52,16 +53,18 @@ or
5253
var ooPatch = window.JSON8Patch
5354
```
5455

55-
For performance concerns JSON8 Patch mutates documents; if you want it to work on its own shallow copy of your document use:
56+
For performance concerns JSON8 Patch may mutate target document; if you want it to use its own copy use:
5657

5758
```javascript
58-
5959
var oo = require('json8')
60-
doc = oo.clone(doc)
60+
var myDocument = {foo: 'bar'}
61+
var doc = oo.clone(myDocument)
6162
```
6263

6364
See [clone](https://github.com/JSON8/JSON8#ooclone).
6465

66+
JSON8 Patch never mutates patches.
67+
6568
[](#json8-pointer)
6669

6770
# Methods
@@ -92,14 +95,41 @@ The revert object can be used to revert a patch on a document.
9295

9396
```javascript
9497
// apply the patch with the reversible option
95-
var patchResult = ooPatch.apply(doc, patch, {reversible: true});
96-
doc = patchResult.doc
98+
var applyResult = ooPatch.apply(doc, patch, {reversible: true});
99+
doc = applyResult.doc
97100

98101
// revert the patch
99-
doc = ooPatch.revert(doc, patchResult.revert).doc;
102+
doc = ooPatch.revert(doc, applyResult.revert).doc;
100103
// doc is strictly identical to the original
101104
```
102105

106+
See also [buildPatchFromRevert](#buildPatchFromRevert) which offers more flexibility.
107+
108+
[](#json8-patch)
109+
110+
## buildPatchFromRevert
111+
112+
Builds a valid JSON Patch from the result of a reversible apply operation.
113+
You can then use this patch with [apply](#apply) method to revert a previously applied patch.
114+
115+
```javascript
116+
var applyResult = ooPatch.apply(doc, patch, {reversible: true});
117+
doc = applyResult.doc
118+
119+
// revert the patch
120+
var revertPatch = ooPatch.buildPatchFromRevert(applyResult.revert) // this is a valid JSON Patch
121+
doc = ooPatch.apply(doc, revertPatch).doc
122+
// doc is strictly identical to the original
123+
```
124+
125+
Because `buildPatchFromRevert + apply` offers more flexibility over `revert` it is preferred.
126+
127+
* use [pack/unpack](#patch-size) with the result of `buildPatchFromRevert` making it ideal for storage or transport
128+
* reverse a revert (and so on...) with `{reversible: true}`
129+
* [diff](#diff) between reverts
130+
* merge multiple reverts into one
131+
* rebase reverts
132+
103133
[](#json8-patch)
104134

105135
## diff

index.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@
22

33
var apply = require('./lib/apply')
44

5+
module.exports.diff = require('./lib/diff')
6+
module.exports.valid = require('./lib/valid')
7+
8+
// Patching
59
module.exports.patch = apply
610
module.exports.apply = apply
11+
12+
// Reverting
713
module.exports.revert = require('./lib/revert')
8-
module.exports.diff = require('./lib/diff')
9-
module.exports.valid = require('./lib/valid')
14+
module.exports.buildPatchFromRevert = require('./lib/buildPatchFromRevert')
1015

1116
// Operations
1217
module.exports.add = require('./lib/add')

lib/apply.js

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,92 @@
11
'use strict'
22

3-
module.exports = require('./patch').apply
3+
var parse = require('json8-pointer').parse
4+
var buildPatchFromRevert = require('./buildPatchFromRevert')
5+
6+
var operations = Object.create(null)
7+
operations.add = require('./add')
8+
operations.copy = require('./copy')
9+
operations.move = require('./move')
10+
operations.remove = require('./remove')
11+
operations.replace = require('./replace')
12+
operations.test = require('./test')
13+
14+
/**
15+
* @typedef PatchResult
16+
* @type Object
17+
* @property {Any} doc - The patched document
18+
* @property {Array} revert - An array to be used with revert or buildPatchFromRevert methods
19+
*/
20+
21+
/**
22+
* Apply a single JSON Patch operation object to a JSON document
23+
* @param {Any} doc - JSON document to apply the patch to
24+
* @param {Object} patch - JSON Patch operation object
25+
* @return {Any}
26+
*/
27+
function run(doc, patch) {
28+
if (typeof patch.path === 'string')
29+
var pathTokens = parse(patch.path)
30+
if (typeof patch.from === 'string')
31+
var fromTokens = parse(patch.from)
32+
33+
switch (patch.op) {
34+
case 'add':
35+
case 'replace':
36+
case 'test':
37+
if (patch.value === undefined)
38+
throw new Error('Missing value parameter')
39+
return operations[patch.op](doc, pathTokens, patch.value)
40+
41+
case 'move':
42+
case 'copy':
43+
return operations[patch.op](doc, fromTokens, pathTokens)
44+
45+
case 'remove':
46+
return operations[patch.op](doc, pathTokens)
47+
}
48+
49+
throw new Error(patch.op + ' isn\'t a valid operation')
50+
}
51+
52+
/**
53+
* Apply a JSON Patch to a JSON document
54+
* @param {Any} doc - JSON document to apply the patch to
55+
* @param {Array} patch - JSON Patch array
56+
* @param {Object} options - options
57+
* @param {Boolean} options.reversible - return an array to revert
58+
* @return {PatchResult}
59+
*/
60+
function apply(doc, patch, options) {
61+
if (!Array.isArray(patch))
62+
throw new Error('Invalid argument, patch must be an array')
63+
64+
var done = []
65+
66+
for (var i = 0, len = patch.length; i < len; i++) {
67+
var p = patch[i]
68+
var r
69+
70+
try {
71+
r = run(doc, p)
72+
}
73+
catch (err) { // restore document
74+
// does not use ./revert.js because it is a circular dependency
75+
var revertPatch = buildPatchFromRevert(done)
76+
apply(doc, revertPatch)
77+
throw err
78+
}
79+
80+
doc = r.doc
81+
done.push([p, r.previous, r.idx])
82+
}
83+
84+
var result = {doc: doc}
85+
86+
if (options && typeof options === 'object' && options.reversible === true)
87+
result.revert = done
88+
89+
return result
90+
}
91+
92+
module.exports = apply

lib/buildPatchFromRevert.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
'use strict'
2+
3+
var JSON8Pointer = require('json8-pointer')
4+
var serialize = JSON8Pointer.serialize
5+
var parse = JSON8Pointer.parse
6+
7+
/**
8+
* Return the reverse operation to a JSON Patch operation
9+
* @param {Object} patch - JSON Patch operation object
10+
* @param {Any} previous - previous value for add and replace operations
11+
* @param {Number} idx - index of the item for array
12+
* @return {Object}
13+
*/
14+
function reverse(patch, previous, idx) {
15+
var op = patch.op
16+
var path = patch.path
17+
18+
if (op === 'copy' || (op === 'add' && previous === undefined)) {
19+
if (idx === undefined)
20+
return {"op": "remove", "path": path}
21+
22+
// for item pushed to array with -
23+
var tokens = parse(path)
24+
tokens[tokens.length - 1] = idx.toString()
25+
return {"op": "remove", "path": serialize(tokens)}
26+
}
27+
if (op === 'replace')
28+
return {"op": "replace", "path": path, "value": previous}
29+
if (op === 'move')
30+
return {"op": "move", "path": patch.from, "from": path}
31+
if (op === 'add' || op === 'remove')
32+
return {"op": "add", "path": path, "value": previous}
33+
if (op === 'test')
34+
return {"op": "test", "path": path, "value": patch.value}
35+
}
36+
37+
/**
38+
* Builds and returns a valid JSON Patch from a revert value
39+
* @param {Array} revert - revert value from the apply or patch method with {reversible: true}
40+
* @return {Array} patches - JSON Patch
41+
*/
42+
module.exports = function buildPatchFromRevert(revert) {
43+
var patch = []
44+
45+
for (var i = 0, len = revert.length; i < len; i++) {
46+
var item = revert[i]
47+
patch.unshift(reverse(item[0], item[1], item[2]))
48+
}
49+
50+
return patch
51+
}

lib/patch.js

Lines changed: 1 addition & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -1,151 +1,3 @@
11
'use strict'
22

3-
var JSON8Pointer = require('json8-pointer')
4-
var parse = JSON8Pointer.parse
5-
var serialize = JSON8Pointer.serialize
6-
7-
var operations = Object.create(null)
8-
operations.add = require('./add')
9-
operations.copy = require('./copy')
10-
operations.move = require('./move')
11-
operations.remove = require('./remove')
12-
operations.replace = require('./replace')
13-
operations.test = require('./test')
14-
15-
/**
16-
* @typedef PatchResult
17-
* @type Object
18-
* @property {Any} doc - The patched document
19-
* @property {Array} revert - An array to be used with revert method
20-
*/
21-
22-
/**
23-
* Apply a single JSON Patch operation object to a JSON document
24-
* @param {Any} doc - JSON document to apply the patch to
25-
* @param {Object} patch - JSON Patch operation object
26-
* @return {Any}
27-
*/
28-
function run(doc, patch) {
29-
if (typeof patch.path === 'string')
30-
var pathTokens = parse(patch.path)
31-
if (typeof patch.from === 'string')
32-
var fromTokens = parse(patch.from)
33-
34-
switch (patch.op) {
35-
case 'add':
36-
case 'replace':
37-
case 'test':
38-
if (patch.value === undefined)
39-
throw new Error('Missing value parameter')
40-
return operations[patch.op](doc, pathTokens, patch.value)
41-
42-
case 'move':
43-
case 'copy':
44-
return operations[patch.op](doc, fromTokens, pathTokens)
45-
46-
case 'remove':
47-
return operations[patch.op](doc, pathTokens)
48-
}
49-
50-
throw new Error(patch.op + ' isn\'t a valid operation')
51-
}
52-
53-
/**
54-
* Return the reverse operation to a JSON Patch operation
55-
* @param {Object} patch - JSON Patch operation object
56-
* @param {Any} previous - previous value for add and replace operations
57-
* @param {Number} idx - index of the item for array
58-
* @return {Object}
59-
*/
60-
function reverse(patch, previous, idx) {
61-
var op = patch.op
62-
var path = patch.path
63-
64-
if (op === 'copy' || (op === 'add' && previous === undefined)) {
65-
if (idx === undefined)
66-
return {"op": "remove", "path": path}
67-
68-
// for item pushed to array with -
69-
var tokens = parse(path)
70-
tokens[tokens.length - 1] = idx.toString()
71-
return {"op": "remove", "path": serialize(tokens)}
72-
}
73-
if (op === 'replace')
74-
return {"op": "replace", "path": path, "value": previous}
75-
if (op === 'move')
76-
return {"op": "move", "path": patch.from, "from": path}
77-
if (op === 'add' || op === 'remove')
78-
return {"op": "add", "path": path, "value": previous}
79-
if (op === 'test')
80-
return {"op": "test", "path": path, "value": patch.value}
81-
}
82-
83-
/**
84-
* Apply a JSON Patch to a JSON document
85-
* @param {Any} doc - JSON document to apply the patch to
86-
* @param {Array} patch - JSON Patch array
87-
* @param {Object} options - options
88-
* @param {Boolean} options.reversible - return an array to revert
89-
* @return {PatchResult}
90-
*/
91-
function apply(doc, patch, options) {
92-
if (!Array.isArray(patch))
93-
throw new Error('Invalid argument, patch must be an array')
94-
95-
var done = []
96-
97-
for (var i = 0, len = patch.length; i < len; i++) {
98-
var p = patch[i]
99-
var r
100-
101-
try {
102-
r = run(doc, p)
103-
}
104-
catch (err) {
105-
revert(doc, done)
106-
throw err
107-
}
108-
109-
doc = r.doc
110-
done.push([p, r.previous, r.idx])
111-
}
112-
113-
var result = {doc: doc}
114-
115-
if (options && typeof options === 'object' && options.reversible === true)
116-
result.revert = done
117-
118-
return result
119-
}
120-
121-
/**
122-
* Return an object that can be use with revert
123-
* @param {Array} items - array of [patch, previous, idx] items
124-
* @return {Array} patches - JSON Patch array
125-
*/
126-
function foo(items) {
127-
var patches = []
128-
129-
for (var i = 0, len = items.length; i < len; i++) {
130-
var item = items[i]
131-
patches.unshift(reverse(item[0], item[1], item[2]))
132-
}
133-
134-
return patches
135-
}
136-
137-
/**
138-
* Revert apply a JSON Patch
139-
* @param {Any} doc - JSON document to which the patch was applied to
140-
* @param {Array} items - array of [patch, previous, idx] items
141-
* @return {PatchResult}
142-
*/
143-
function revert(doc, items) {
144-
var patches = foo(items)
145-
return apply(doc, patches)
146-
}
147-
148-
module.exports.foo = foo
149-
module.exports.apply = apply
150-
module.exports.revert = revert
151-
module.exports.reverse = reverse
3+
module.exports = require('./patch')

0 commit comments

Comments
 (0)