Skip to main content

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

json_source_edit-0.2.0.tar.gz (38.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

json_source_edit-0.2.0-py3-none-any.whl (19.9 kB view details)

Uploaded Python 3

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

Hashes for json_source_edit-0.2.0.tar.gz
Algorithm Hash digest
SHA256 379eed4bac728d5ff0cb383377a948a922569e5815ab7b8eeacf58cfe30f68c8
MD5 100ec9acccddda6320a353282d2d5005
BLAKE2b-256 d9541d2bd94b2bfd8714569355c7e6ace332c877dea2d6b6e18c50b6cb9de337

See more details on using hashes here.

Provenance

The following attestation bundles were made for json_source_edit-0.2.0.tar.gz:

Publisher: publish.yml on jlumbroso/json-source-edit

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file json_source_edit-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for json_source_edit-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 aa4a2de9bbbdfc78a8eb7679b20ad7e0855f81b5806f9a9a8fe4ed3fe1ae9aee
MD5 137fc2be077307d5b40d1ea71f5500e1
BLAKE2b-256 d526b078fd05774000e29ed5ab6789fdde10599b07a0edc42a8057f2f20cb8bb

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page