Nested-Diff.py
Recursive diff and patch for nested structures.
Main features
- Machine readable diff structure.
- Human friendly diff visualization, collapsible html diffs.
- All ops (added/removed/changed/unchanged) are optional and may be disabled.
- Any data types support may be added by external handlers.
Install
pip install nested_diff
For extra formats support (YAML, TOML) in cli tools, use
pip install nested_diff[cli]
Command line tools
$ cat a.json b.json
[0, [1], 3]
[0, [1, 2], 3]
$ nested_diff a.json b.json
[1]
+ [1]
+ 2
nested_diff a.json b.json --ofmt json > patch.json
nested_patch a.json patch.json
Library usage
>>> from nested_diff import diff, patch
>>> from nested_diff.formatters import TextFormatter
>>>
>>> a = {'one': 1, 'two': 2, 'three': 3}
>>> b = {'one': 1, 'two': 42}
>>>
>>>
>>> full_diff = diff(a, b)
>>> full_diff
{'D': {'three': {'R': 3}, 'two': {'N': 42, 'O': 2}, 'one': {'U': 1}}}
>>>
>>> short_diff = diff(a, b, O=False, U=False) # omit old and unchanged items
>>> short_diff
{'D': {'three': {'R': 3}, 'two': {'N': 42}}}
>>>
>>>
>>> a = patch(a, short_diff)
>>> assert a == b
>>>
>>>
>>> human_readable = TextFormatter().format(full_diff)
>>> print(human_readable)
{'one'}
1
- {'three'}
- 3
{'two'}
- 2
+ 42
<BLANKLINE>
>>>
HTML and ANSI colored terminal formatters also available out of the box.
See Live Demo,
HOWTO and
nested_diff.formatters.
Diff structure
Diff is a dict and may contain status keys:
Astands for 'added', it's value - added item.Dmeans 'different' and contains subdiff.Nis a new value for changed item.Ois a changed item's old value.Rkey used for removed item.Urepresent unchanged item.
and auxiliary keys:
Ccomment; optional, value - arbitrary string.Eextension ID (optional).Iindex for sequence item, used only when prior item was omitted.
Diff metadata alternates with actual data; simple types specified as is, dicts,
lists and tuples contain subdiffs for their items with native for such types
addressing: indexes for lists and tuples, keys for dictionaries. Any status
key, except D may be omitted during diff computation. E key is used with
D when entity unable to contain diff by itself (set, frozenset for example);
D contain a list of subdiffs in this case.
Annotated example
a: {"one": [5,7]}
b: {"one": [5], "two": 2}
opts: U=False # omit unchanged items
diff:
{"D": {"one": {"D": [{"I": 1, "R": 7}]}, "two": {"A": 2}}}
| | | | | | || | | | | | | | |
| | | | | | || | | | | | | | +- with value 2
| | | | | | || | | | | | | +- key 'two' was added
| | | | | | || | | | | | +- subdiff for it
| | | | | | || | | | | +- another key from top-level
| | | | | | || | | | +- what it was (item's value: 7)
| | | | | | || | | +- what happened to item (removed)
| | | | | | || | +- list item's actual index
| | | | | | || +- prior item was omitted
| | | | | | |+- subdiff for list item
| | | | | | +- it's value - list
| | | | | +- it is deeply changed
| | | | +- subdiff for key 'one'
| | | +- it has key 'one'
| | +- top-level thing is a dict
| +- changes somewhere deeply inside
+- diff is always a dict
License
Licensed under the terms of the Apache License, Version 2.0.
See Also
Release files for nested-diff 1.11.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| nested_diff-1.11.0.tar.gz | 23.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| nested_diff-1.11.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 51.1 kB
Release files / nested_diff-1.11.0.tar.gz
| Download URL | nested_diff-1.11.0.tar.gz |
|---|---|
| Size | 23.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
163835219567f3a0275eb6ae480d94c4b61c572643f4da4f1bb36906d8076b28
|
|
BLAKE2b-256 checksum How to use checksums |
1df8cc0e805803eb37f49b53dcf087c0ee744d69cb1493709da9d3ac52af4f89
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 22, 2026.
Transparency logRelease files / nested_diff-1.11.0-py3-none-any.whl
| Download URL | nested_diff-1.11.0-py3-none-any.whl |
|---|---|
| Size | 27.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3e0cd2d23aa7b31c0978db0f399314eecc8acc03d171eb27ced66cc801c30e2a
|
|
BLAKE2b-256 checksum How to use checksums |
ef8edbd4b97ec8f386816f4b4e270900589cc018294e204db93102eac600bbdd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 22, 2026.
Transparency log