json_patch

Synopsis

json_patch [--output FILE] [--unified | --normal]
           [--strip [NUM]] [--reverse] [originalfile] [patchfile]
json_patch [--version]
json_patch [--help]

Description

json_patch applies diffs in the format produced by json_diff(1) to JSON-serialized data structures.

The program attempts to mimic the interface of the patch(1) utility as far as possible, while also remaining compatible with the script functionality of the json_delta.py library on which it relies. There are, therefore, at least four different ways its input can be specified.

  1. The simplest, of course, is if the filenames are both specified as positional arguments.
  2. Closely following in terms of simplicity, the inputs can be fed as a JSON array [<structure>,<patch>] to standard input.
  3. If only one positional argument is specified, it is read as the filename of the original data structure, and the patch is expected to appear on stdin.
  4. Finally, if there are no positional arguments, and stdin cannot be parsed as JSON, it can alternatively be a udiff, as output by json_diff -u. In this case, json_patch will read the name of the file containing the structure to modify out of the first header line of the udiff (the one beginning with ---).

The most salient departure from the behavior of patch(1) is that, by default, json_patch will not modify files in place. Instead, the patched structure is written as JSON to stdout. Frankly, this is to save having to implement backup filename options, getting it wrong, and having angry hackers blame me for their lost data.

However, the input structure is read into memory before the output file handle is opened, so an in-place modification can be accomplished by setting the option --output to point to <originalfile>.

Also, note that json_diff and json_patch can only manipulate a single file at a time: even the output of json_diff -u is not a “unified” diff sensu stricto.

json_patch will accept input in any of the encodings specified in RFC 7159, namely UTF-8, -16 or -32, with or without byte-order marks. The default encoding for output is UTF-8 with no BOM, but this can be changed using the --encoding option.

Options

--output FILE, -o FILE
 Write output to FILE instead of stdout.
--unified, -u Force the patch to be interpreted as a udiff.
--normal, -n Force the patch to be interpreted as a normal (i.e. JSON-format) patch
--reverse, -R Assume the patch was created with old and new files swapped.
--strip NUM, -p NUM
 Strip NUM leading components from file names read out of udiff headers.
--encoding ENCODING
 Select the encoding for the output.
--version Show the program’s version number and exit.
--help, -h Show a brief help message and exit.

Udiff Format

The program has strict requirements of the format of “unified” diffs. It works by discarding header lines, then creating two strings: one by discarding every line beginning with -, then discarding the first character of every remaining line, and one following the same procedure, but with lines beginning with + discarded. For json_patch to function, these strings must be interpretable according to the following superset of the JSON spec:

  • Within objects, the string ... may appear in any context where a "property": <object> construction would be valid JSON. This indicates that one or more properties have been omitted from the representation of the object.
  • Within arrays, the string ... may appear as an array element. It may optionally be followed by an integer in parentheses, e.g. (1), (15). This indicates that that number of elements have been omitted from the array, or that one element has, if no parenthesized number is present.

The program reconstructs the JSON-format diff on the basis of these strings, and then applies it to the input structure.

Implementation Notes

The value of the --encoding option in the Python implementation of json_diff is fed straight to the encode() function, so it is possible to get output in any encoding supported by the Python implementation used to run the script. This makes various mildly interesting things possible, like getting compressed output using --encoding bz2 or --encoding zlib, or even --encoding rot-13 (Furrfu!)