@broofa/jsondiff
Advanced tools
Comparing version 1.3.0 to 1.3.1
58
index.js
@@ -5,2 +5,8 @@ // Reserved values | ||
/** | ||
* Normalize a patch value by converting DROP values to undefined. This is | ||
* useful for doing code such as `if (jsondiff.value(patch.someValue)) ...` | ||
* | ||
* @param {any} value | ||
*/ | ||
function value(val) { | ||
@@ -10,2 +16,10 @@ return val === DROP ? undefined : val; | ||
/** | ||
* Generate a patch object that describes the difference between two states | ||
* | ||
* @param {any} before | ||
* @param {any} after | ||
* | ||
* @returns {any} Patch object as described in the README | ||
*/ | ||
function diff(before, after) { | ||
@@ -61,11 +75,19 @@ if (after === undefined) return DROP; | ||
function patch(before, _diff) { | ||
if (_diff === DROP) return undefined; | ||
if (_diff === KEEP) _diff = before; | ||
if (_diff == null) return _diff; | ||
/** | ||
* Apply a patch object to some 'before' state and return the 'after' state | ||
* | ||
* @param {any} before | ||
* @param {any} _patch | ||
* | ||
* @returns {any} The mutated state | ||
*/ | ||
function patch(before, _patch) { | ||
if (_patch === DROP) return undefined; | ||
if (_patch === KEEP) _patch = before; | ||
if (_patch == null) return _patch; | ||
if (before === _diff) return before; | ||
if (before === _patch) return before; | ||
const beforeType = before == null ? 'null' : before.constructor.name; | ||
const type = _diff.constructor.name; | ||
const type = _patch.constructor.name; | ||
@@ -76,3 +98,3 @@ if (beforeType !== type) { | ||
case 'Array': before = []; break; | ||
default: return _diff; | ||
default: return _patch; | ||
} | ||
@@ -88,3 +110,3 @@ } | ||
case 'Date': // Not strictly JSON but useful | ||
if (before.getTime() == _diff.getTime()) _diff = before; | ||
if (before.getTime() == _patch.getTime()) _patch = before; | ||
break; | ||
@@ -95,4 +117,4 @@ | ||
const values = {...before}; | ||
for (const k in _diff) { | ||
if (value(_diff[k]) === undefined) { | ||
for (const k in _patch) { | ||
if (value(_patch[k]) === undefined) { | ||
if (k in values) { | ||
@@ -103,3 +125,3 @@ delete values[k]; | ||
} else { | ||
const val = patch(before[k], _diff[k]); | ||
const val = patch(before[k], _patch[k]); | ||
if (val !== before[k]) { | ||
@@ -112,3 +134,3 @@ values[k] = val; | ||
_diff = isEqual ? before : values; | ||
_patch = isEqual ? before : values; | ||
break; | ||
@@ -118,6 +140,6 @@ } | ||
case 'Array': { | ||
const values = new Array(_diff.length); | ||
let isEqual = before.length === _diff.length; | ||
for (let i = 0, l = _diff.length; i < l; i++) { | ||
const val = patch(before[i], _diff[i]); | ||
const values = new Array(_patch.length); | ||
let isEqual = before.length === _patch.length; | ||
for (let i = 0, l = _patch.length; i < l; i++) { | ||
const val = patch(before[i], _patch[i]); | ||
@@ -127,3 +149,3 @@ if (val !== before[i]) isEqual = false; | ||
} | ||
_diff = isEqual ? before : values; | ||
_patch = isEqual ? before : values; | ||
break; | ||
@@ -135,3 +157,3 @@ } | ||
} | ||
return before === _diff ? before : _diff; | ||
return before === _patch ? before : _patch; | ||
}; | ||
@@ -138,0 +160,0 @@ |
{ | ||
"name": "@broofa/jsondiff", | ||
"version": "1.3.0", | ||
"version": "1.3.1", | ||
"description": "Pragmatic, intuitive diffing and patching of JSON objects", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
156
README.md
@@ -7,61 +7,116 @@ <!-- | ||
Pragmatic, intuitive diffing and patching of JSON objects | ||
Pragmatic and intuitive diff and patch functions for JSON data | ||
## Summary of related modules | ||
## Installation | ||
There are variety of modules available that can diff and patch JSON data | ||
structures. Here's a quick run down: | ||
`npm install @broofa/jsondiff` | ||
| Module | Size | Patch format | Notes | | ||
|---|---|---|---| | ||
| **@broofa/jsondiff** | 0.7K | Overlay | Readable patches | | ||
| deep-diff | 3.5K | RFC9602-like | Most popular module | | ||
| rfc9602 | 2K | RFC9602 | See below | | ||
| fast-json-patch | 4K | RFC9602 | See below | | ||
## Usage | ||
The main difference between these modules is in the patch object structure. Most | ||
(all?) of the other modules use a structure based on or similar to RFC9602, | ||
which is composed of a series of operations that describe how to transform the | ||
target object. In contrast, the patches in this module act as an "overlay" | ||
that is copied onto the target object. There are tradeoffs to this, some | ||
good, some bad: | ||
Require it: | ||
1. **Readability** - A structured patch is easier to read because it "looks" | ||
like the object it's modifying. | ||
2. **Reordering** - Data is not "moved" in a structured patch. It is simply | ||
deleted from the old location and inserted at the new location. | ||
2. **Size** - Operation-based patches are more verbose (lots of duplicate keys | ||
and values), except in the case where values move locations. This may have a | ||
significant impact on network bandwidth, especially for uncompressed data | ||
streams. | ||
3. **Fault tolerance** - Operation-based patches may fail if operations are | ||
applied out of order or if the target object does not have the expected | ||
structure. | ||
4. **DROP/KEEP hack** - See comments about `DROP` and `KEEP` values in "Patch | ||
Objects", below. This may be off-putting to some readers. | ||
```javascript | ||
const jsondiff = require('@broofa/jsondiff'); | ||
## Installation | ||
// ... or ES6 module style: | ||
// import jsondiff from '@broofa/jsondiff'; | ||
`npm install @broofa/jsondiff` | ||
``` | ||
## Usage | ||
Start with some `before` and `after` state: | ||
```javascript | ||
console.log(before); | ||
⇒ { name: 'my object', | ||
⇒ description: 'it\'s an object!', | ||
⇒ details: { it: 'has', an: 'array', with: [ 'a', 'few', 'elements' ] } } | ||
``` | ||
```javascript | ||
const jsondiff = require('@broofa/jsondiff'); | ||
console.log(after); | ||
const before = {a: 'Hello', b: 'you', c: ['big', 'bad'], d: 'beast'}; | ||
const after = {a: 'Hi', c: ['big', 'bad', 'bold'], d: 'beast'}; | ||
⇒ { name: 'updated object', | ||
⇒ title: 'it\'s an object!', | ||
⇒ details: | ||
⇒ { it: 'has', | ||
⇒ an: 'array', | ||
⇒ with: [ 'a', 'few', 'more', 'elements', { than: 'before' } ] } } | ||
``` | ||
// Create a patch | ||
// Note the use of DROP (-) and KEEP(+) values | ||
const patch = jsondiff.diff(before, after); // ⇨ { a: 'Hi', b: '-', c: [ '+', '+', 'bold' ] } | ||
Create a patch that descibes the difference between the two: | ||
```javascript | ||
const patch = jsondiff.diff(before, after); | ||
console.log(patch); | ||
// Apply it to the original | ||
const patched = jsondiff.patch(before, patch); // ⇨ { a: 'Hi', c: [ 'big', 'bad', 'bold' ], d: 'beast' } | ||
⇒ { name: 'updated object', | ||
⇒ description: '-', | ||
⇒ details: | ||
⇒ { with: [ '+', '+', 'more', 'elements', { than: 'before' } ] }, | ||
⇒ title: 'it\'s an object!' } | ||
``` | ||
*(Note the special DROP and KEEP values ("-" and "+")! These are explained in **Patch Objects**, below.)* | ||
// Get the expected result | ||
assert.deepEqual(after, patched); // Passes! | ||
Apply `patch` to the before state to reproduce the `after` state: | ||
```javascript | ||
const patched = jsondiff.patch(before, patch); | ||
console.log(patched); | ||
⇒ { name: 'updated object', | ||
⇒ details: | ||
⇒ { it: 'has', | ||
⇒ an: 'array', | ||
⇒ with: [ 'a', 'few', 'more', 'elements', { than: 'before' } ] }, | ||
⇒ title: 'it\'s an object!' } | ||
``` | ||
## Why yet-another diff module? | ||
There are already several modules in this space - `deep-diff`, `rfc6902`, or `fast-json-patch`, to name a few. `deep-diff` is the most popular, however `rfc6902` is (to my mind) the most compelling because it will interoperate with other libraries that support [RFC6902 standard](https://tools.ietf.org/html/rfc6902). | ||
However ... the patch formats used by these modules tends to be cryptic and overly verbose - | ||
a list of the mutations needed to transform between the two states. In the case | ||
of `deep-diff` you end up with this patch: | ||
```javascript | ||
console.log(deepPatch); | ||
⇒ [ { kind: 'E', | ||
⇒ path: [ 'name' ], | ||
⇒ lhs: 'my object', | ||
⇒ rhs: 'updated object' }, | ||
⇒ { kind: 'D', path: [ 'description' ], lhs: 'it\'s an object!' }, | ||
⇒ { kind: 'A', | ||
⇒ path: [ 'details', 'with' ], | ||
⇒ index: 4, | ||
⇒ item: { kind: 'N', rhs: [ [Function: Object] ] } }, | ||
⇒ { kind: 'A', | ||
⇒ path: [ 'details', 'with' ], | ||
⇒ index: 3, | ||
⇒ item: { kind: 'N', rhs: 'elements' } }, | ||
⇒ { kind: 'E', | ||
⇒ path: [ 'details', 'with', 2 ], | ||
⇒ lhs: 'elements', | ||
⇒ rhs: 'more' }, | ||
⇒ { kind: 'N', path: [ 'title' ], rhs: 'it\'s an object!' } ] | ||
``` | ||
And for `rfc6902`: | ||
```javascript | ||
console.log(rfcPatch); | ||
⇒ [ { op: 'remove', path: '/description' }, | ||
⇒ { op: 'add', path: '/title', value: 'it\'s an object!' }, | ||
⇒ { op: 'replace', path: '/name', value: 'updated object' }, | ||
⇒ { op: 'add', path: '/details/with/2', value: 'more' }, | ||
⇒ { op: 'add', path: '/details/with/-', value: { than: 'before' } } ] | ||
``` | ||
The advantage(?) of this module is that the patch structure mirrors the | ||
structure of the target data. As such, it terse, readable, and resilient. | ||
That said, this module may not be for everyone. In particular, readers may find | ||
the DROP and KEEP values (described below) to be... "interesting". | ||
## API | ||
@@ -82,7 +137,18 @@ | ||
### jsondiff.merge(before, after) | ||
### jsondiff.value(val) | ||
Shorthand for `jsondiff.patch(before, jsondiff.diff(before, after))`. Useful | ||
for mutating an object only where values have actually changed. | ||
Normalize patch values. Currently this just converts `DROP` values to | ||
`undefined`, otherwise returns the value. This is useful in determining if a | ||
patch has a meaningful value. E.g. | ||
```javascript | ||
const newPatch = {foo: jsondiff.DROP, bar: 123}; | ||
newPatch.foo; // ⇨ '-' | ||
jsondiff.value(newPatch.foo); // ⇨ undefined | ||
jsondiff.value(newPatch.bar); // ⇨ 123 | ||
jsondiff.value(newPatch.whups); // ⇨ undefined | ||
``` | ||
## Patch Objects | ||
@@ -89,0 +155,0 @@ |
```javascript --hide --run usage | ||
runmd.onRequire = path => path.replace(/^@broofa\/\w+/, '..'); | ||
const assert = require('assert'); | ||
``` | ||
# @broofa/jsondiff | ||
const before = { | ||
name: 'my object', | ||
description: 'it\'s an object!', | ||
details: { | ||
it: 'has', | ||
an: 'array', | ||
with: ['a', 'few', 'elements'] | ||
} | ||
}; | ||
Pragmatic, intuitive diffing and patching of JSON objects | ||
const after = { | ||
name: 'updated object', | ||
title: 'it\'s an object!', | ||
details: { | ||
it: 'has', | ||
an: 'array', | ||
with: ['a', 'few', 'more', 'elements', { than: 'before' }] | ||
} | ||
}; | ||
## Summary of related modules | ||
const deepPatch = [ | ||
{kind: 'E', path: ['name'], lhs: 'my object', rhs: 'updated object'}, | ||
{kind: 'D', path: ['description'], lhs: 'it\'s an object!'}, | ||
{kind: 'A', path: ['details', 'with'], index: 4, item: {kind: 'N', rhs: [Object]}}, | ||
{kind: 'A', path: ['details', 'with'], index: 3, item: {kind: 'N', rhs: 'elements'}}, | ||
{kind: 'E', path: ['details', 'with', 2], lhs: 'elements', rhs: 'more' }, | ||
{kind: 'N', path: ['title'], rhs: 'it\'s an object!'} | ||
]; | ||
There are variety of modules available that can diff and patch JSON data | ||
structures. Here's a quick run down: | ||
const rfcPatch = [ | ||
{op: 'remove', path: '/description'}, | ||
{op: 'add', path: '/title', value: 'it\'s an object!'}, | ||
{op: 'replace', path: '/name', value: 'updated object'}, | ||
{op: 'add', path: '/details/with/2', value: 'more'}, | ||
{op: 'add', path: '/details/with/-', value: {than: 'before'}} | ||
]; | ||
| Module | Size | Patch format | Notes | | ||
|---|---|---|---| | ||
| **@broofa/jsondiff** | 0.7K | Overlay | Readable patches | | ||
| deep-diff | 3.5K | RFC9602-like | Most popular module | | ||
| rfc9602 | 2K | RFC9602 | See below | | ||
| fast-json-patch | 4K | RFC9602 | See below | | ||
``` | ||
The main difference between these modules is in the patch object structure. Most | ||
(all?) of the other modules use a structure based on or similar to RFC9602, | ||
which is composed of a series of operations that describe how to transform the | ||
target object. In contrast, the patches in this module act as an "overlay" | ||
that is copied onto the target object. There are tradeoffs to this, some | ||
good, some bad: | ||
# @broofa/jsondiff | ||
1. **Readability** - A structured patch is easier to read because it "looks" | ||
like the object it's modifying. | ||
2. **Reordering** - Data is not "moved" in a structured patch. It is simply | ||
deleted from the old location and inserted at the new location. | ||
2. **Size** - Operation-based patches are more verbose (lots of duplicate keys | ||
and values), except in the case where values move locations. This may have a | ||
significant impact on network bandwidth, especially for uncompressed data | ||
streams. | ||
3. **Fault tolerance** - Operation-based patches may fail if operations are | ||
applied out of order or if the target object does not have the expected | ||
structure. | ||
4. **DROP/KEEP hack** - See comments about `DROP` and `KEEP` values in "Patch | ||
Objects", below. This may be off-putting to some readers. | ||
Pragmatic and intuitive diff and patch functions for JSON data | ||
@@ -49,19 +54,58 @@ ## Installation | ||
Require it: | ||
```javascript --run usage | ||
const jsondiff = require('@broofa/jsondiff'); | ||
const before = {a: 'Hello', b: 'you', c: ['big', 'bad'], d: 'beast'}; | ||
const after = {a: 'Hi', c: ['big', 'bad', 'bold'], d: 'beast'}; | ||
// ... or ES6 module style: | ||
// import jsondiff from '@broofa/jsondiff'; | ||
``` | ||
// Create a patch | ||
// Note the use of DROP (-) and KEEP(+) values | ||
const patch = jsondiff.diff(before, after); // RESULT | ||
Start with some `before` and `after` state: | ||
```javascript --run usage | ||
console.log(before); | ||
``` | ||
// Apply it to the original | ||
const patched = jsondiff.patch(before, patch); // RESULT | ||
```javascript --run usage | ||
console.log(after); | ||
``` | ||
// Get the expected result | ||
assert.deepEqual(after, patched); // Passes! | ||
Create a patch that descibes the difference between the two: | ||
```javascript --run usage | ||
const patch = jsondiff.diff(before, after); | ||
console.log(patch); | ||
``` | ||
*(Note the special DROP and KEEP values ("-" and "+")! These are explained in **Patch Objects**, below.)* | ||
Apply `patch` to the before state to reproduce the `after` state: | ||
```javascript --run usage | ||
const patched = jsondiff.patch(before, patch); | ||
console.log(patched); | ||
``` | ||
## Why yet-another diff module? | ||
There are already several modules in this space - `deep-diff`, `rfc6902`, or `fast-json-patch`, to name a few. `deep-diff` is the most popular, however `rfc6902` is (to my mind) the most compelling because it will interoperate with other libraries that support [RFC6902 standard](https://tools.ietf.org/html/rfc6902). | ||
However ... the patch formats used by these modules tends to be cryptic and overly verbose - | ||
a list of the mutations needed to transform between the two states. In the case | ||
of `deep-diff` you end up with this patch: | ||
```javascript --run usage | ||
console.log(deepPatch); | ||
``` | ||
And for `rfc6902`: | ||
```javascript --run usage | ||
console.log(rfcPatch); | ||
``` | ||
The advantage(?) of this module is that the patch structure mirrors the | ||
structure of the target data. As such, it terse, readable, and resilient. | ||
That said, this module may not be for everyone. In particular, readers may find | ||
the DROP and KEEP values (described below) to be... "interesting". | ||
## API | ||
@@ -82,7 +126,17 @@ | ||
### jsondiff.merge(before, after) | ||
### jsondiff.value(val) | ||
Shorthand for `jsondiff.patch(before, jsondiff.diff(before, after))`. Useful | ||
for mutating an object only where values have actually changed. | ||
Normalize patch values. Currently this just converts `DROP` values to | ||
`undefined`, otherwise returns the value. This is useful in determining if a | ||
patch has a meaningful value. E.g. | ||
```javascript --run usage | ||
const newPatch = {foo: jsondiff.DROP, bar: 123}; | ||
newPatch.foo; // RESULT | ||
jsondiff.value(newPatch.foo); // RESULT | ||
jsondiff.value(newPatch.bar); // RESULT | ||
jsondiff.value(newPatch.whups); // RESULT | ||
``` | ||
## Patch Objects | ||
@@ -89,0 +143,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
21112
334
170