# Nested-Diff.py
Recursive diff and patch for nested structures.
[![PyPi](https://img.shields.io/pypi/v/nested-diff.svg)](https://pypi.python.org/pypi/nested-diff)
[![Tests](https://github.com/mr-mixas/Nested-Diff.py/actions/workflows/tests.yml/badge.svg)](https://github.com/mr-mixas/Nested-Diff.py/actions?query=branch%3Amaster)
[![Coverage](https://coveralls.io/repos/github/mr-mixas/Nested-Diff.py/badge.svg)](https://coveralls.io/github/mr-mixas/Nested-Diff.py)
[![Supported Python versions](https://img.shields.io/pypi/pyversions/nested_diff.svg)](https://pypi.org/project/nested_diff/)
[![License](https://img.shields.io/pypi/l/nested_diff.svg)](https://pypi.org/project/nested_diff/)
## 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.
**[See Live Demo!](https://nesteddiff.pythonanywhere.com/)**
## Install
`pip install nested_diff`
For extra formats support (YAML, TOML) in cli tools, use
`pip install nested_diff[cli]`
## Command line tools
```sh
$ cat a.json b.json
[0, [1], 3]
[0, [1, 2], 3]
```
```sh
$ nested_diff a.json b.json
[1]
+ [1]
+ 2
```
```sh
nested_diff a.json b.json --ofmt json > patch.json
nested_patch a.json patch.json
```
## Library usage
```py
>>> 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](https://nesteddiff.pythonanywhere.com/),
[HOWTO](https://github.com/mr-mixas/Nested-Diff.py/blob/master/HOWTO.md) and
[nested\_diff.formatters](https://github.com/mr-mixas/Nested-Diff.py/tree/master/nested_diff/formatters.py).
## Diff structure
Diff is a dict and may contain status keys:
* `A` stands for 'added', it's value - added item.
* `D` means 'different' and contains subdiff.
* `N` is a new value for changed item.
* `O` is a changed item's old value.
* `R` key used for removed item.
* `U` represent unchanged item.
and auxiliary keys:
* `C` comment; optional, value - arbitrary string.
* `E` extension ID (optional).
* `I` index 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
```text
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](http://www.apache.org/licenses/LICENSE-2.0).
## See Also
[HOWTO](https://github.com/mr-mixas/Nested-Diff.py/blob/master/HOWTO.md)
[deepdiff](https://pypi.org/project/deepdiff/),
[jsondiff](https://pypi.org/project/jsondiff/),
[jsonpatch](https://pypi.org/project/jsonpatch/),
[json-delta](https://pypi.org/project/json-delta/)
Raw data
{
"_id": null,
"home_page": null,
"name": "nested-diff",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.7",
"maintainer_email": null,
"keywords": "diff, nested-diff, recursive-diff, nested-data, data-structures",
"author": null,
"author_email": "Michael Samoglyadov <mixas.sr@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/65/26/a2fdfc6596dbd944005505593dfe10b505ece7bfa62362fe1706374ca2b0/nested_diff-1.6.0.tar.gz",
"platform": null,
"description": "# Nested-Diff.py\n\nRecursive diff and patch for nested structures.\n\n[![PyPi](https://img.shields.io/pypi/v/nested-diff.svg)](https://pypi.python.org/pypi/nested-diff)\n[![Tests](https://github.com/mr-mixas/Nested-Diff.py/actions/workflows/tests.yml/badge.svg)](https://github.com/mr-mixas/Nested-Diff.py/actions?query=branch%3Amaster)\n[![Coverage](https://coveralls.io/repos/github/mr-mixas/Nested-Diff.py/badge.svg)](https://coveralls.io/github/mr-mixas/Nested-Diff.py)\n[![Supported Python versions](https://img.shields.io/pypi/pyversions/nested_diff.svg)](https://pypi.org/project/nested_diff/)\n[![License](https://img.shields.io/pypi/l/nested_diff.svg)](https://pypi.org/project/nested_diff/)\n\n## Main features\n\n* Machine readable diff structure.\n* Human friendly diff visualization, collapsible html diffs.\n* All ops (added/removed/changed/unchanged) are optional and may be disabled.\n* Any data types support may be added by external handlers.\n\n**[See Live Demo!](https://nesteddiff.pythonanywhere.com/)**\n\n## Install\n\n`pip install nested_diff`\n\nFor extra formats support (YAML, TOML) in cli tools, use\n\n`pip install nested_diff[cli]`\n\n## Command line tools\n\n```sh\n$ cat a.json b.json\n[0, [1], 3]\n[0, [1, 2], 3]\n```\n\n```sh\n$ nested_diff a.json b.json\n [1]\n+ [1]\n+ 2\n```\n\n```sh\nnested_diff a.json b.json --ofmt json > patch.json\nnested_patch a.json patch.json\n```\n\n## Library usage\n\n```py\n>>> from nested_diff import diff, patch\n>>> from nested_diff.formatters import TextFormatter\n>>>\n>>> a = {'one': 1, 'two': 2, 'three': 3}\n>>> b = {'one': 1, 'two': 42}\n>>>\n>>>\n>>> full_diff = diff(a, b)\n>>> full_diff\n{'D': {'three': {'R': 3}, 'two': {'N': 42, 'O': 2}, 'one': {'U': 1}}}\n>>>\n>>> short_diff = diff(a, b, O=False, U=False) # omit old and unchanged items\n>>> short_diff\n{'D': {'three': {'R': 3}, 'two': {'N': 42}}}\n>>>\n>>>\n>>> a = patch(a, short_diff)\n>>> assert a == b\n>>>\n>>>\n>>> human_readable = TextFormatter().format(full_diff)\n>>> print(human_readable)\n {'one'}\n 1\n- {'three'}\n- 3\n {'two'}\n- 2\n+ 42\n<BLANKLINE>\n>>>\n```\n\nHTML and ANSI colored terminal formatters also available out of the box. \nSee [Live Demo](https://nesteddiff.pythonanywhere.com/),\n[HOWTO](https://github.com/mr-mixas/Nested-Diff.py/blob/master/HOWTO.md) and\n[nested\\_diff.formatters](https://github.com/mr-mixas/Nested-Diff.py/tree/master/nested_diff/formatters.py).\n\n## Diff structure\n\nDiff is a dict and may contain status keys:\n\n* `A` stands for 'added', it's value - added item.\n* `D` means 'different' and contains subdiff.\n* `N` is a new value for changed item.\n* `O` is a changed item's old value.\n* `R` key used for removed item.\n* `U` represent unchanged item.\n\nand auxiliary keys:\n\n* `C` comment; optional, value - arbitrary string.\n* `E` extension ID (optional).\n* `I` index for sequence item, used only when prior item was omitted.\n\nDiff metadata alternates with actual data; simple types specified as is, dicts,\nlists and tuples contain subdiffs for their items with native for such types\naddressing: indexes for lists and tuples, keys for dictionaries. Any status\nkey, except `D` may be omitted during diff computation. `E` key is used with\n`D` when entity unable to contain diff by itself (set, frozenset for example);\n`D` contain a list of subdiffs in this case.\n\n### Annotated example\n\n```text\na: {\"one\": [5,7]}\nb: {\"one\": [5], \"two\": 2}\nopts: U=False # omit unchanged items\n\ndiff:\n{\"D\": {\"one\": {\"D\": [{\"I\": 1, \"R\": 7}]}, \"two\": {\"A\": 2}}}\n| | | | | | || | | | | | | | |\n| | | | | | || | | | | | | | +- with value 2\n| | | | | | || | | | | | | +- key 'two' was added\n| | | | | | || | | | | | +- subdiff for it\n| | | | | | || | | | | +- another key from top-level\n| | | | | | || | | | +- what it was (item's value: 7)\n| | | | | | || | | +- what happened to item (removed)\n| | | | | | || | +- list item's actual index\n| | | | | | || +- prior item was omitted\n| | | | | | |+- subdiff for list item\n| | | | | | +- it's value - list\n| | | | | +- it is deeply changed\n| | | | +- subdiff for key 'one'\n| | | +- it has key 'one'\n| | +- top-level thing is a dict\n| +- changes somewhere deeply inside\n+- diff is always a dict\n```\n\n## License\n\nLicensed under the terms of the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0).\n\n## See Also\n\n[HOWTO](https://github.com/mr-mixas/Nested-Diff.py/blob/master/HOWTO.md)\n\n[deepdiff](https://pypi.org/project/deepdiff/),\n[jsondiff](https://pypi.org/project/jsondiff/),\n[jsonpatch](https://pypi.org/project/jsonpatch/),\n[json-delta](https://pypi.org/project/json-delta/)\n\n",
"bugtrack_url": null,
"license": null,
"summary": "Recursive diff and patch for nested structures.",
"version": "1.6.0",
"project_urls": {
"Homepage": "https://github.com/mr-mixas/Nested-Diff.py",
"Repository": "https://github.com/mr-mixas/Nested-Diff.py.git"
},
"split_keywords": [
"diff",
" nested-diff",
" recursive-diff",
" nested-data",
" data-structures"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "567214133a8c18fa4fe1b4be4b206244a8d83d9c3a5a2d6f1d4b3a541aae4165",
"md5": "b29d1288ff9884662fc3f449919ee302",
"sha256": "236895d56d64b39123034321bd819bf77006632fd96d0095b02e5183fce1f8ba"
},
"downloads": -1,
"filename": "nested_diff-1.6.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "b29d1288ff9884662fc3f449919ee302",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.7",
"size": 25990,
"upload_time": "2024-09-01T12:41:04",
"upload_time_iso_8601": "2024-09-01T12:41:04.943465Z",
"url": "https://files.pythonhosted.org/packages/56/72/14133a8c18fa4fe1b4be4b206244a8d83d9c3a5a2d6f1d4b3a541aae4165/nested_diff-1.6.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "6526a2fdfc6596dbd944005505593dfe10b505ece7bfa62362fe1706374ca2b0",
"md5": "8557cd9756bfb4c0c1f34664b2e336b5",
"sha256": "2d9eea19595d69380ac51686627bb54cc2bdb6f72fbca2d7fd6dd8838f31d1df"
},
"downloads": -1,
"filename": "nested_diff-1.6.0.tar.gz",
"has_sig": false,
"md5_digest": "8557cd9756bfb4c0c1f34664b2e336b5",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.7",
"size": 22311,
"upload_time": "2024-09-01T12:41:07",
"upload_time_iso_8601": "2024-09-01T12:41:07.065836Z",
"url": "https://files.pythonhosted.org/packages/65/26/a2fdfc6596dbd944005505593dfe10b505ece7bfa62362fe1706374ca2b0/nested_diff-1.6.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-09-01 12:41:07",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "mr-mixas",
"github_project": "Nested-Diff.py",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"tox": true,
"lcname": "nested-diff"
}