If data["id"] raises KeyError for an external API's JSON response, the code has revealed a missing field or a changed data contract. Replacing every bracket lookup with get can hide the problem. Decide which keys are required and which are optional.
data = {"id": 7, "name": "Book"}
name = data["name"] # Required value
nickname = data.get("nickname") # Optional value
assert name == "Book"
assert nickname is NoneBracket lookup exposes a required-field contract
A missing required key, a missing optional key, and an actual value of 0 are different states. As the diagram shows, giving them all the same default can erase the meaning of the response.
A missing id needs validation; a missing optional nickname can be None; an actual count: 0 must remain the numeric zero.
Without a required ID or valid type, later processing may be wrong. Turn a missing key into a meaningful validation error and record only safe information about which field was absent. Continuing from the data dictionary above:
if "id" not in data:
raise ValueError("response is missing id")
item_id = data["id"]Change the sample to data = {"name": "Book"} and this check stops before an invalid ID reaches storage. Put that failure in a unit test. Avoid logging the entire response body; a status code and required-key presence may be sufficient diagnostics.
A default passed to get has meaning
data.get("count", 0) merges “count is missing” with “count is actually zero.” If those states matter, keep a missing value as None or represent it separately. In nested JSON, check the type at each level too:
payload = {"stats": {"count": 0}}
stats = payload.get("stats")
if not isinstance(stats, dict):
raise ValueError("stats must be an object")
count = stats.get("count")
assert count == 0For many required fields, validate once at the input boundary and pass checked values inward instead of inventing a default for each missing key. Silently treating a malformed response as success can corrupt later reports or stored data.
Test present, optional-missing, and required-missing cases
import json
def read_item(raw):
data = json.loads(raw)
if not isinstance(data, dict):
raise ValueError("expected a JSON object")
if "id" not in data:
raise ValueError("id is required")
return data["id"], data.get("nickname")
assert read_item('{"id": 7, "nickname": "book"}') == (7, "book")
assert read_item('{"id": 7}') == (7, None)
try:
read_item('{"nickname": "book"}')
except ValueError as error:
assert str(error) == "id is required"
else:
raise AssertionError("missing required id was not detected")The first two checks cover a present and absent optional key. The last confirms that a missing required key fails immediately, with the expected error. For fields that allow 0 or an empty string, test presence separately from value validity.
Log safe diagnostics rather than entire responses
Successful JSON parsing does not mean the response has the expected shape. Validate types, nested structures, and allowed values at the boundary. In error logs, prefer safe request identifiers, status codes, and which required fields are present to whole request bodies or authentication data.
Before adding a default, check whether it changes the meaning of existing data. Merging “missing” and 0 can quietly distort aggregates. A default is a convenience, not permission to continue with invalid input.
Key takeaways
data["key"] fails visibly for a missing required key; get is useful for optional values. Define required fields, optional fields, and defaults in the data contract, then validate and test each outcome so API response changes are detected promptly.

