Safe In-Place JSON Patch Without External Dependencies

Often you only need to change one leaf value inside a JSON file while leaving every sibling intact. Below are the practical patterns that achieve this in pure Python, followed by a lightweight helper that keeps the code footprint minimal yet avoids the "load-modify-dump everything" anti-pattern.

Quick Motivating Example

{
  "name": "张三",
  "age": 18
}

Goal: bump age to 21 without touching name.

Approaches at a Glance

  1. Read-then-replace – simplest, but you rewrite the whole file even when only one field changes.
  2. Named callback – works, yet feels verbose because Python’s lambda is restricted to a single expression.
  3. Third-party helpers (e.g. pydash) – ergonomic, but pulling in a libray for a single patch is overkill.
  4. Hand-rolled path walker – what we’ll implement below.

Minimal Path-Based Updater

The idea is to tarverse the structure using a list of keys / indices, mutate the final container in place, then persist the change.

import json
from pathlib import Path
from datetime import datetime
from typing import Any, List, Union

def patch_json(path: Path, pointer: List[Union[str, int]], value: Any) -> bool:
    """
    Update a single leaf in a JSON document located at *pointer*.

    pointer: list of keys or list indices, e.g. ["users", 0, "profile", "age"]
    returns: True if the leaf was found and updated, else False
    """
    try:
        with path.open(encoding="utf-8") as fp:
            doc = json.load(fp)
    except (FileNotFoundError, json.JSONDecodeError) as exc:
        print(f"Cannot read {path}: {exc}")
        return False

    cursor = doc
    try:
        *parents, last = pointer
        for step in parents:
            cursor = cursor[step]          # walk down
        cursor[last] = value               # mutate in place
    except (KeyError, IndexError, TypeError) as exc:
        print(f"Invalid pointer {pointer}: {exc}")
        return False

    try:
        with path.open("w", encoding="utf-8") as fp:
            json.dump(doc, fp, indent=2, ensure_ascii=False)
    except OSError as exc:
        print(f"Cannot write {path}: {exc}")
        return False

    return True

Usage Demo

DB = Path("users.json")

# bootstrap sample file
DB.write_text(json.dumps([
    {"hika": {"latest_login": None, "email": "h.nagi@example.com"}, "id": 1},
    {"xxgal": {"latest_login": None, "email": "x.gal@example.com"}, "id": 2}
], indent=2), encoding="utf-8")

print("Before:", DB.read_text())

patch_json(DB, [0, "hika", "latest_login"], datetime.now().isoformat(timespec="seconds"))

print("After:", DB.read_text())

Runnning the snippet shows only latest_login changed; every other field, ordering, and formatting remains untouched.

Edge Cases & Tips

  • When the leaf does not exist, the helper returns False instead of creating the path; change the logic if auto-creation is desired.
  • Lists and dicts are handled transparently; mixed structures such as {"items": [{"meta": {...}}]} work out of the box.
  • If atomicity matters, write to a temporary file and os.replace it to avoid half-written data on crash.

For most everyday scripts the above 20-line utility is all you need; no external dependency, no full-document rewrite, and the call site stays a one-liner.

Tags: JSON python path-pointer in-place-update file-io

Posted on Fri, 02 Oct 2026 16:30:16 +0000 by Taneya