Surgical, byte-preserving JSON editing — change only what you intend to change.
Project description
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() defaults to validate=False; save() defaults to validate=True
— writing to disk is where a mistake actually costs you something, so that
path checks unless you opt out.
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, move, copy |
Not implemented — raise NotImplementedError. Planned next (see CHANGELOG.md) |
Multiple replace/remove/test 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 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. 203 tests, 100% line coverage. See
CHANGELOG.md for release history and the Scope table above for what's
implemented.
License
MIT.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file json_source_edit-0.2.0.tar.gz.
File metadata
- Download URL: json_source_edit-0.2.0.tar.gz
- Upload date:
- Size: 38.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
379eed4bac728d5ff0cb383377a948a922569e5815ab7b8eeacf58cfe30f68c8
|
|
| MD5 |
100ec9acccddda6320a353282d2d5005
|
|
| BLAKE2b-256 |
d9541d2bd94b2bfd8714569355c7e6ace332c877dea2d6b6e18c50b6cb9de337
|
Provenance
The following attestation bundles were made for json_source_edit-0.2.0.tar.gz:
Publisher:
publish.yml on jlumbroso/json-source-edit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
json_source_edit-0.2.0.tar.gz -
Subject digest:
379eed4bac728d5ff0cb383377a948a922569e5815ab7b8eeacf58cfe30f68c8 - Sigstore transparency entry: 2319997901
- Sigstore integration time:
-
Permalink:
jlumbroso/json-source-edit@60eda500804d9fbd39ec946e67b011fe3d46e97a -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/jlumbroso
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@60eda500804d9fbd39ec946e67b011fe3d46e97a -
Trigger Event:
release
-
Statement type:
File details
Details for the file json_source_edit-0.2.0-py3-none-any.whl.
File metadata
- Download URL: json_source_edit-0.2.0-py3-none-any.whl
- Upload date:
- Size: 19.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
aa4a2de9bbbdfc78a8eb7679b20ad7e0855f81b5806f9a9a8fe4ed3fe1ae9aee
|
|
| MD5 |
137fc2be077307d5b40d1ea71f5500e1
|
|
| BLAKE2b-256 |
d526b078fd05774000e29ed5ab6789fdde10599b07a0edc42a8057f2f20cb8bb
|
Provenance
The following attestation bundles were made for json_source_edit-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on jlumbroso/json-source-edit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
json_source_edit-0.2.0-py3-none-any.whl -
Subject digest:
aa4a2de9bbbdfc78a8eb7679b20ad7e0855f81b5806f9a9a8fe4ed3fe1ae9aee - Sigstore transparency entry: 2319998085
- Sigstore integration time:
-
Permalink:
jlumbroso/json-source-edit@60eda500804d9fbd39ec946e67b011fe3d46e97a -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/jlumbroso
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@60eda500804d9fbd39ec946e67b011fe3d46e97a -
Trigger Event:
release
-
Statement type: