The JSON-delta API

This document is intended to describe the behaviour of the main entry points for every implementation of JSON-delta. For now, it effectively documents the top-level namespace of the Python implementation, as that is the most fully-developed implementation in the suite.

Core functions

json_delta.diff(left_struc, right_struc, minimal=None, verbose=True, key=None, array_align=True, compare_lengths=True, common_key_threshold=0.0)

Compose a sequence of diff stanzas sufficient to convert the structure left_struc into the structure right_struc. (The goal is to add ‘necessary and’ to ‘sufficient’ above!).

Optional parameters:

verbose: Print compression statistics to stderr, and warn if the setting of minimal contradicts the other parms.

array_align: Use _diff.needle_diff() to compute deltas between arrays. Relatively computationally expensive, but likely to produce shorter diffs. Defaults to True.

compare_lengths: If [[key, right_struc]] can be encoded as a shorter JSON-string, return it instead of examining the internal structure of left_struc and right_struc. It involves calling json.dumps() twice for every node in the structure, but may result in smaller diffs. Defaults to True.

common_key_threshold: Skip recursion into left_struc and right_struc if the fraction of keys they have in common (with the same value) is less than this parm (which should be a float between 0.0 and 1.0). Defaults to 0.0.

minimal: Included for backwards compatibility. True is equivalent to (array_align=True, compare_lengths=True, common_key_threshold=0.0); False is equivalent to (array_align=False, compare_lengths=False, common_key_threshold=0.5). Specific settings of array_align, compare_lengths or common_key_threshold will supersede this parm, warning on stderr if verbose and minimal are both set.

key: Also included for backwards compatibility. If set, will be prepended to the key in each stanza of the output.

The parameter key is present because this function is mutually recursive with _diff.needle_diff() and _diff.keyset_diff(). If set to a list, it will be prefixed to every keypath in the output.

json_delta.patch(struc, diff, in_place=True)

Apply the sequence of diff stanzas diff to the structure struc.

By default, this function modifies struc in place; set in_place to False to return a patched copy of struc instead:

>>> will_change = [16]
>>> wont_change = [16]
>>> patch(will_change, [[[0]]])
[]
>>> will_change
[]
>>> patch(wont_change, [[[0]]], False)
[]
>>> wont_change
[16]
json_delta.udiff(left, right, patch=None, indent=0, use_ellipses=True, entry=True)

Render the difference between the structures left and right as a string in a fashion inspired by diff -u.

Generating a udiff is strictly slower than generating a normal diff with the same option parameters, since the udiff is computed on the basis of a normal diff between left and right. If such a diff has already been computed (e.g. by calling diff()), pass it as the patch parameter:

>>> (next(udiff({"foo": None}, {"foo": None}, patch=[])) ==
...  ' {...}')
True

As you can see above, structures that are identical in left and right are abbreviated using '...' by default. To disable this behavior, set use_ellipses to False.

>>> ('\n'.join(udiff({"foo": None}, {"foo": None},
...            patch=[], use_ellipses=False)) ==
... """ {
...  "foo":
...    null
...  }""")
True
>>> ('\n'.join(udiff([None, None, None], [None, None, None],
...             patch=[], use_ellipses=False)) ==
... """ [
...   null,
...   null,
...   null
...  ]""")
True
json_delta.upatch(struc, udiff, reverse=False, in_place=True)

Apply a patch as output by json_delta.udiff() to struc.

As with json_delta.patch(), struc is modified in place by default. Set the parm in_place to False if this is not the desired behaviour.

The udiff format has enough information in it that this transformation can be applied in reverse: i.e. if udiff is the output of udiff(left, right), you can reconstruct right given left and udiff (by running upatch(left, udiff)), or you can also reconstruct left given right and udiff (by running upatch(right, udiff, reverse=True)). This is not possible for JSON-format diffs, since a [keypath] stanza (meaning “delete the structure at keypath”) does not record what the deleted structure was.

load_and_*

For convenience when handling input that is already JSON-serialized, implementations should offer entry points named load_and_{FUNC}, which deserialize their input and then apply {FUNC} to it.

json_delta.load_and_diff(left=None, right=None, both=None, array_align=None, compare_lengths=None, common_key_threshold=None, minimal=None, verbose=True)

Apply diff() to strings or files representing JSON-serialized structures.

Specify either left and right, or both, like so:

>>> (load_and_diff('{"foo":"bar"}', '{"foo":"baz"}', verbose=False)
...  == [[["foo"],"baz"]])
True
>>> (load_and_diff(both='[{"foo":"bar"},{"foo":"baz"}]', verbose=False)
...  == [[["foo"],"baz"]])
True

left, right and both may be either strings (instances of basestring in 2.7) or file-like objects.

minimal and verbose are passed through to diff(), which see.

A call to this function with string arguments is strictly equivalent to calling diff(json.loads(left), json.loads(right), minimal=minimal, verbose=verbose) or diff(*json.loads(both), minimal=minimal, verbose=verbose), as appropriate.

json_delta.load_and_patch(struc=None, stanzas=None, both=None)

Apply patch() to strings or files representing JSON-serialized structures.

Specify either struc and stanzas, or both, like so:

>>> (load_and_patch('{"foo":"bar"}', '[[["foo"],"baz"]]') ==
...  {"foo": "baz"})
True
>>> (load_and_patch(both='[{"foo":"bar"},[[["foo"],"baz"]]]') ==
...  {"foo": "baz"})
True

struc, stanzas and both may be either strings (instances of basestring in 2.7) or file-like objects.

A call to this function with string arguments is strictly equivalent to calling patch(json.loads(struc), json.loads(stanzas), in_place=in_place) or patch(*json.loads(both), in_place=in_place), as appropriate.

json_delta.load_and_udiff(left=None, right=None, both=None, stanzas=None, indent=0)

Apply udiff() to strings representing JSON-serialized structures.

Specify either left and right, or both, like so:

>>> udiff = """ {
...  "foo":
... -  "bar"
... +  "baz"
...  }"""
>>> test = load_and_udiff('{"foo":"bar"}', '{"foo":"baz"}')
>>> '\n'.join(test) == udiff
True
>>> test = load_and_udiff(both='[{"foo":"bar"},{"foo":"baz"}]')
>>> '\n'.join(test) == udiff
True

left, right and both may be either strings (instances of basestring in 2.7) or file-like objects.

stanzas and indent are passed through to udiff(), which see.

A call to this function with string arguments is strictly equivalent to calling udiff(json.loads(left), json.loads(right), stanzas=stanzas, indent=indent) or udiff(*json.loads(both), stanzas=stanzas, indent=indent), as appropriate.

json_delta.load_and_upatch(struc=None, json_udiff=None, both=None, reverse=False)

Apply upatch() to strings representing JSON-serialized structures.

Specify either struc and json_udiff, or both, like so:

>>> struc = '{"foo":"bar"}'
>>> json_udiff = r'" {\n  \"foo\":\n-  \"bar\"\n+  \"baz\"\n }"'
>>> both = r'[{"foo":"baz"}," '\
... r'{\n  \"foo\":\n-  \"bar\"\n+  \"baz\"\n }"]'
>>> load_and_upatch(struc, json_udiff) == {"foo": "baz"}
True
>>> load_and_upatch(both=both, reverse=True) == {"foo": "bar"}
True

struc, json_udiff and both may be either strings (instances of basestring in 2.7) or file-like objects. Note that json_udiff is so named because it must be a JSON-serialized representation of the udiff string, not the udiff string itself.

reverse is passed through to upatch(), which see.

A call to this function with string arguments is strictly equivalent to calling upatch(json.loads(struc), json.loads(json_udiff), reverse=reverse, in_place=in_place) or upatch(*json.loads(both), reverse=reverse, in_place=in_place), as appropriate.