Batu Lab NotesPractical developer guides

Differentiate a JSON null from an omitted mapping key

By Batu · English technical notes

Differentiate a JSON null from an omitted mapping key

To differentiate JSON null from an omitted mapping key, inspect key membership before using the decoded value: "note" not in patch means omission, while patch["note"] is None means an explicit null. This matters when a JSON object is an update request. In this example, omission retains an existing note, whereas explicit null clears it.

The exact inputs are {} and {"note": null}. json.loads turns them into {} and {'note': None} respectively. The first status report shows that the decoder preserved key presence. The second report demonstrates a concrete failure mode: a naïve get("note") or existing expression treats explicit null as if the caller wanted to retain the old value. That expression would also mishandle an intentionally supplied empty string, so it is unsuitable for this contract.

apply_note_update makes the narrow three-way rule explicit: absent key retains, present null clears, and a present string replaces. Its assertions establish only the outcomes for these synthetic inputs; they do not validate a broader API schema or authorization policy. Python’s standard decoder maps JSON objects to dict and JSON null to None, as documented in the JSON decoder conversion table. No newer APIs are required; this example runs on Python 3.6+.

AI assistance disclosure: this article was drafted with AI assistance and uses synthetic in-memory values.

import json

existing = {"note": "keep"}
omitted = json.loads("{}")
present_null = json.loads('{"note": null}')


def note_state(patch):
    if "note" not in patch:
        return "omitted"
    if patch["note"] is None:
        return "present-null"
    return "present-value"


def naive_update(current, patch):
    return patch.get("note") or current["note"]


def apply_note_update(current, patch):
    if "note" not in patch:
        return current["note"]
    if patch["note"] is None:
        return None
    if not isinstance(patch["note"], str):
        raise TypeError("note must be a string or null")
    return patch["note"]


assert note_state(omitted) == "omitted"
assert note_state(present_null) == "present-null"
assert naive_update(existing, present_null) == "keep"
assert apply_note_update(existing, omitted) == "keep"
assert apply_note_update(existing, present_null) is None

print("decoded states:", note_state(omitted), note_state(present_null))
print("naive null update:", naive_update(existing, present_null))
print("contract omitted:", apply_note_update(existing, omitted))
print("contract null:", apply_note_update(existing, present_null))
decoded states: omitted present-null
naive null update: keep
contract omitted: keep
contract null: None