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 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.

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.3.0.tar.gz (50.9 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.3.0-py3-none-any.whl (28.5 kB view details)

Uploaded Python 3

File details

Details for the file json_source_edit-0.3.0.tar.gz.

File metadata

  • Download URL: json_source_edit-0.3.0.tar.gz
  • Upload date:
  • Size: 50.9 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.3.0.tar.gz
Algorithm Hash digest
SHA256 8585e1adf3add36d3a67cb030c4250542ff0d0ab61e48b2215db18472db2a4a9
MD5 dfbe5728830397570aea2d611c67f10d
BLAKE2b-256 1c8188ccf189abb5a058e5680878f7801814a39dcc609d05e0259468478940ca

See more details on using hashes here.

Provenance

The following attestation bundles were made for json_source_edit-0.3.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.3.0-py3-none-any.whl.

File metadata

File hashes

Hashes for json_source_edit-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ad4c5c2ee42b567421c3f7809717e3f43572b947217e5a9a0fa5199812958e4b
MD5 09b575896adfc40bb6bd55a12af6f3ec
BLAKE2b-256 c910b1a5799a1ebc2550de10f58094734f2f72f69b7cfcf63750009c01444b44

See more details on using hashes here.

Provenance

The following attestation bundles were made for json_source_edit-0.3.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