This module was designed and written as a means to locate changes to rather big JSON objects/trees and store them efficiently. Essentially this is a re-imagining of UN\*X [`diff`](https://en.wikipedia.org/wiki/Diff) and [`patch`](https://en.wikipedia.org/wiki/Patch_(Unix)) utilities to support JavaScript object trees in addition to flat text, storing the diff/patch data natively in JSON. The specific use cases (*[ImageGrid](https://github.com/flynx/ImageGrid) and [pWiki](https://github.com/flynx/pWiki)*) required additional features not provided by the available at the time alternative implementations (listing in next section).
For a walkthrough by example see the [introduction](#introduction), for general documentation start at the [motivation](#motivation) section or look through the index below.
- The array in `"path"` of the third change is the *index* of the change in the input `"skills"` arrays where each element (`[2, 0]` and `[2, 1]`) describes the spot in the array that changed in the corresponding input object. Each element consists of two items, the first is the actual *index* or position of the change (in both cases `2`) and the second is the length of the change (`0` and `1` respectively, meaning that in `A` we have zero or no items and in `B` one),
-`"A"` or `"B"` may not be present in the change (change #3) if the change describes simple item addition or removal,
- The format is redundant in places by design, for example here you can both infer `"skills"`*length* from the *changes* applied to it and we have an explicit `["path", "length"]`*change*. This is mainly to support cases where inferring array length from changes to it may not be possible, like for sparse arrays.
Now, we can do different things with this information (*diff object*).
### Applying changes (*patch*)
```javascript
// let's clone Bill, just in case...
var Bill2 = JSON.parse(JSON.stringify(Bill))
// now apply the patch...
diff.patch(Bill2)
```
Since we applied *all* the changes to `Bill2`, now he is just another `Ted` (or rather `Ted`'s copy):
Clone the `Diff` constructor, useful for extending or tweaking the type handlers (see: [Extending](#extending-diff) below).
`Diff.fromJSON(json) -> diff`
Build a diff object from JSON (exported via `.json()`).
### Diff object API
`diff.patch(X) -> X'`
Apply "diff* (or *patch*) `X` to `X'` state.
`diff.unpatch(X') -> X`
Undo *diff" application to `X'` returning it to `X` state.
This is equivalent to: `diff.reverse().patch(X')`
~~`diff.check(X) -> bool`~~ (work in progress)
Check if *diff* is compatible/applicable to `X`. This essentially checks if the *left hand side* of the *diff* matches the corresponding nodes of `X`.
`diff.reverse() -> diff`
Generate a new *child diff* where `A` and `B` are reversed.
`diff.filter(path | filter) -> diff`
Generate a new *child diff* leaving only changes that match the `path`/`filter`
The `path` is a `"/"` or `"\"` separated string that supports the following item syntax:
-`"*"` - matches any item (same as: `ANY`).
-`"**"` - matches 0 or more items.
-`"a|b"` - matches either `a` or `b` (same as: `OR('a', 'b')`)
-`"!a"` - matches anything but `a` (smae as: `NOT('a')`)
*Note that `"**"` is a special case in that it can not be combined with other patterns above (e.g. in `"a|**"` the string `"**"` is treated literally and has no special meaning).*
`diff.merge(diff) -> diff`
Generate a merged *diff* containing the changes from both diff object.
*Note that this method currently simply concatenates the changes of two diff objects together, at this point no effort is made to optimize the new change list (changes can be redundant or canceling but there should not be any conflicts unless a merged diff is missing or is out of order).*
`diff.end() -> diff`
Return the *parent diff* that was used to generate the current *child diff* or the current diff if there is not parent.
`diff.json() -> JSON`
Serialize the *diff* to JSON. Note that the output may or may not be JSON compatible depending on the inputs.
The object support can be split into two, basic objects that are stored as-is and containers that support item changes when their types match.
All JavaScript objects/values are supported in the basic form / as-is.
Containers that support item changes include:
-`Object`
-`Array`
- ~~`Map` / `WeakMap`~~ *(planned but not done yet)*
- ~~`Set` / `WeakSet`~~ *(planned but not done yet)*
Additionally attribute changes are supported for all non basic objects (i.e. any `x` for which `x instanceof Object` is `true`). This can be disabled by setting `options.no_attributes` to `true` (see: [Options](#options) below).
At first glance they may seem to be equivalent but in reality they are quite different as in the first pattern `OR(..)` matches the `'x'` key *and* also matches the `'y'` key and thus `AT(..)` will match iff *all* of the matched keys (existing) contain `1`, while the second pattern will match if at least one of `'x'` or `'y'` is `1`.
Also note that the first pattern not equivalent to `AND(AT('x', 1), AT('y', 1))` as `AND(..)` requires that *both*`'x'` and `'y'` exist and contain `1` and first pattern will match if at least one of the keys exists and all the existing keys contain `1`.
Note that the output of `.json()` may not be JSON compatible if the input objects are not json compatible. The reasoning here is simple: `object-diff` is a *diffing* module and not a *serializer*.
The simple way to put this is: if the inputs are JSON-compatible the output of `.json()` is going to also be JSON-compatible.
The big picture is a bit more complicated, `Diff(..)` and friends support allot more than simply JSON, this would include any types, attributes on all objects and loops/recursive structures.
`.fromJSON(..)` does not care about JSON compatibility and will be happy with any output of `.json()`.
## Extending Diff
Create a new diff constructor:
```javascript
var ExtendedDiff = diff.Diff.clone()
```
This has the same API as `Diff` and inherits from it, but it has an independent handler map that can be manipulated without affecting the original `Diff` setup.
When building a *diff* type checking is done in two stages:
1. via the `.check(..)` method of each implementing handler, this approach is used for *synthetic* type handlers, as an exmple look at `'Text'` that matches long multi-line string objects.
2. type-checking via `instanceof` / `.construtor`, this is used for JavaScript objects like `Array` and `Object` instances, for example.
Hence we have two types of handler objects, those that implement `.check(..)` and can have any arbitrary object as a key (though a nice and readable string is recommended), and objects that have the constructor as key against which `instanceof` checks are done.
`.check(..)` has priority to enable handlers to intercept handling of special cases, `'Text'` handler would be a good example.
If types of the not equal object pair mismatch `'Basic'` is used and both are stored in the *diff* as-is.
`.priority` enables sorting of checks and handlers within a stage, can be set to a positive, negative `Number` or `null`, priorities with same numbers are sorted in order of occurrence.
Adding new *synthetic* type handler:
```javascript
ExtendedDiff.types.set('SomeType', {
// Type check priority (optional)...
//
// Types are checked in order of occurrence in .handlers unless
// type .priority is set to a non 0 value.
//
// Default priorities:
// Text: 100
// Needs to run checks before 'Basic' as its targets are
// long strings that 'Basic' also catches.
// Basic: 50
// Needs to be run before we do other object checks.
// Object: -100
// Needs to run after everything else as it will match any
// set of objects.
//
// General guide:
// >50 - to be checked before 'Basic'
// <50and>0 - after Basic but before unprioritized types
// <=50 and <0-afterunprioritizedtypesbutbeforeObject
// <=100 - to be checked after Object -- this is a bit
// pointless in JavaScript.
//
// NOTE: when this is set to 0, then type will be checked in
// order of occurrence...
priority: null,
// If set to true will disable additional attribute diffing on
// matching objects...
no_attributes: false | true,
// Check if obj is compatible (optional)...
//
// .check(obj[, options])
// -> bool
//
compatible: function(obj, options){
// ...
},
// Handle/populate the diff of A and B...
//
// Input diff format:
// {
// type: <type-name>,
// }
//
handle: function(obj, diff, A, B, options){
// ...
},
// Walk the diff...
//
// This will pass each change to func(..) and return its result...
//
// .walk(diff, func, path)
// -> res
//
// NOTE: by default this will not handle attributes (.attrs), so
// if one needs to handle them Object's .walk(..) should be
// This is useful to cleanup and do any final modifications.
//
// This is expected to return the result.
//
// see: 'Text' for an example.
postPatch: function(result){
..
return result
},
// Reverse the change in diff...
//
reverse: function(change){
// ...
},
})
```
Adding new type handler (checked via `instanceof` / `.constructor`):
```javascript
ExtendedDiff.types.set(SomeOtherType, {
priority: null,
// NOTE: .check(..) is not needed here...
// the rest of the methods are the same as before...
// ...
})
```
Remove an existing type handler:
```javascript
ExtendedDiff.types.delete('Text')
```
The [source code](./diff.js#L1098) is a good example for this as the base `Diff(..)` is built using this API, but note that we are registering types on the `Types` object rather than on the `Diff` itself, there is no functional difference other than how the main code is structured internally.
The handler methods are all called in the context of the `Diff.types` instance, this instance is created per `Diff`'s method call and is destroyed right after the method is done, thus it is save to use the context for caching.
To call a different type handler's methods use:
```javascript
this.typeCall(HandlerType, 'method', ...args)
```
For an example see: `Object` handler's `.walk(..)` in [diff.js](./diff.js#L1178).