json-source-edit
A Python package for surgical, byte-preserving JSON editing. Change only what you intend to change — everything else in the file survives byte-for-byte, so your diff shows your edit, not a full-file rewrite.
The problem
The obvious way to edit a JSON file is parse → modify → serialize:
data = json.loads(text)
data["steps"][1]["line"] = 433
text = json.dumps(data, indent=2)
This is correct, and it destroys your diff. Real evidence, from the project this package was extracted out of: three simple changes to one file — two field updates and one deletion — were expected to produce a ~10-line diff. The parse-modify-serialize round-trip produced a 500+ line diff instead.
That's because json.dumps doesn't know or care what your original
formatting was — indentation, key order, quote style, all of it gets
re-decided from scratch, every time, whether or not you touched it.
Multiply that by a codebase-sized JSON file and code review becomes
impossible: every line looks changed, and the actual edit is buried.
json-source-edit fixes this at the source: it never re-serializes the
document. It computes exact byte positions for each change against the
original text and splices replacement bytes into the gaps — the same
operation a film or tape splicer performs, cut and rejoin at an exact point,
everything else on the reel undisturbed.
Install
pip install json-source-edit
(Or clone the repo and pip install -e . for local development.)
Usage
from json_source_edit import JSONEditor
editor = JSONEditor.from_string("""{
"title": "Rust Basics",
"steps": [
{"file": "src/main.rs", "line": 10},
{"file": "src/lib.rs", "line": 20}
]
}""")
editor.replace("/steps/0/line", 433)
editor.remove("/steps/1")
result = editor.apply(validate=True)
print(editor.preview_diff())
--- original
+++ modified
@@ -1,7 +1,6 @@
{
"title": "Rust Basics",
"steps": [
- {"file": "src/main.rs", "line": 10},
- {"file": "src/lib.rs", "line": 20}
+ {"file": "src/main.rs", "line": 433}
]
}
validate=True catches mistakes before they reach you. It reconstructs the
expected result independently — replaying your edits against a copy of the
parsed document — and diffs that against what the surgical edit actually
produced. If they disagree, it raises SemanticValidationError instead of
silently returning something subtly wrong.
apply(), commit(), and save() all default to validate=True — pass
validate=False to skip both checks and get the raw compiled result
regardless, e.g. if you're doing your own validation or need the ~10-15%
extra time this costs off the critical path (apply()'s default changed
from False in 0.5.0, after a batch that emptied a container and added
into it was found to silently return invalid JSON on every release from
v0.1.0 through v0.4.0 — see docs/adr/0010-*.md).
Three more methods help you inspect a pending batch before committing to
it: get_value, get_modifications, and preview_diff.
Scope
| JSON Patch operation | Status |
|---|---|
replace |
Supported — any path, any depth |
remove |
Supported — array elements and object properties, any depth, except the document root itself (structurally undefined: the root has no parent container to apply a comma/whitespace rule against) |
test |
Supported — not a text edit, an assertion. Evaluated against the document as it stood before this batch's own operations (not a naive sequential reading of RFC 6902 — see the docstring on operations.Test), with type-strict comparison (1 does not test-equal 1.0 or true) |
add |
Supported — new array index (any position, or - to append) or object key. An existing object key is replaced instead, per RFC 6902 — an existing array index is always an insert, never a replace (see docs/adr/0002-*.md). The document root is out of scope, same posture as remove. |
move |
Supported — RFC 6902 §4.4, a remove composed with an add. Rejects moving a location into one of its own children, and moving a location to itself. |
copy |
Supported — RFC 6902 §4.5, an add using a value resolved (and deep-copied) from elsewhere in the document. |
Every JSON Patch operation is implemented. Multiple replace/remove/
test/add/move/copy calls batch correctly against the same original
document — every path resolves in original-document coordinates
regardless of what else is in the batch, so edit order doesn't matter and
one deletion or insertion can't corrupt another edit's position.
Benchmark (v0)
Reproduce with python benchmarks/throughput.py — a standalone script, no
test framework required.
Methodology: replaces 5% of a flat array's elements at each size, median of 5 runs, reporting throughput against the original document size (the more relevant number for "can this handle my file" than edit count alone).
Single-machine numbers, not a controlled benchmark environment — expect real variance run to run and machine to machine; rerun locally before relying on any of this for capacity planning.
| Size | Elements | Document | Edits | Median time | Throughput |
|---|---|---|---|---|---|
| small | 200 | 0.01MB | 10 | 0.65ms | 15,353 edits/sec, 13.7 MB/sec |
| medium | 2,000 | 0.09MB | 100 | 6.25ms | 15,990 edits/sec, 14.9 MB/sec |
| large | 20,000 | 0.97MB | 1000 | 87.22ms | 11,465 edits/sec, 11.1 MB/sec |
(Measured on macOS/arm64, Python 3.12.4, 2026-08-01 — see the script's own output for full per-run samples; the "large" row in particular showed bimodal timing on this machine, a real observation, not smoothed over.)
Provenance
This package was designed and hardened inside
codetour-cli, across four
independent review gates, before being extracted here with no known
remaining incompleteness. See PROVENANCE.md for the
commit-by-commit history and docs/adr/0001-extraction-from-codetour-cli.md
for the extraction decisions themselves (import strategy, the dependency
verdict, the supported-Python-versions floor).
Credits
The position-mapping this package edits against — turning a JSON document
into exact byte offsets for every value and key — is done by
json-source-map, a small,
dependency-free library by David Andersson. It's the one piece of this
package that isn't ours: everything in src/json_source_edit/source_map.py
is a thin seam around it, and everything else in this package builds on top
of what it computes. Elegant, narrowly-scoped libraries like this are what
make a package like this one feasible to write at all.
Status
Published: pypi.org/project/json-source-edit.
pip install json-source-edit. 243 tests, 100% line coverage, full JSON
Patch operation coverage. See CHANGELOG.md for release history. Design
decisions beyond the original extraction live in docs/adr/ (0002:
add's insertion doctrine, including the move/copy compositions;
0003: test's pristine-batch semantics).
License
MIT.
Metadata
Release files for json-source-edit 0.5.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 | |
|---|---|---|---|
| json_source_edit-0.5.0.tar.gz | 73.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| json_source_edit-0.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 115.8 kB
Release files / json_source_edit-0.5.0.tar.gz
| Download URL | json_source_edit-0.5.0.tar.gz |
|---|---|
| Size | 73.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f5f2eff651578f29b54d9f6b6a59ede25b1a9b5a289b552582e239d337f4628d
|
|
BLAKE2b-256 checksum How to use checksums |
8fcfd56ba6b69d23f4f87f35ba7dc5334dddd51b5c47586b51b7664a4980f8f4
|
| 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 5, 2026.
Transparency logRelease files / json_source_edit-0.5.0-py3-none-any.whl
| Download URL | json_source_edit-0.5.0-py3-none-any.whl |
|---|---|
| Size | 42.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3485d96d5da92ce1b008592e9c60f62c46225dc4b02b26f3874790a4b019a70f
|
|
BLAKE2b-256 checksum How to use checksums |
b572cbcf558760d5f751f76d8bcca83092691904a77a58b100c6d43eebabf9ec
|
| 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 5, 2026.
Transparency log