A usable API result passes several boundaries. First, did an HTTP response report a successful status? Next, does its metadata name a representation the client supports? Can the bytes be decoded as the required text? Is that text valid JSON under the chosen parsing policy? Finally, does the decoded value have the endpoint's promised shape? Success at one boundary does not imply success at the next.
Order those checks to preserve useful explanations. An HTML 503 is first an HTTP error, not a mysterious JSON error. A 204 or 205 is valid no-content success and needs no JSON parser. An ordinary 200 with a wrong content type fails representation selection before text interpretation. The build gives each failure a stable code instead of returning an ambiguous empty list.
JSON is a data format rather than executable Python. Its object maps to
dict, array to list, null to None, and booleans to True/False. A top-level
number or array can be valid JSON while still being wrong for this endpoint.
Never use eval as a substitute for json.loads.
import json
value = json.loads('{"items":[{"id":7}],"next":null}')
print(type(value).__name__, value["next"] is None)
print(all(isinstance(item, dict) for item in value["items"]))
wrong_shape = json.loads('{"items":[null]}')
print(all(isinstance(item, dict) for item in wrong_shape["items"]))This API requires UTF-8 bytes. Decode explicitly with strict error handling
before parsing so invalid bytes produce encoding. This is a declared endpoint
contract; Python's JSON library can accept additional encodings when handed
bytes directly. Catch the narrow error for the operation you actually run.
Do not replace arbitrary programming errors with “empty successful result.”
Python's defaults accept spelled NaN/Infinity constants outside standard
JSON and retain only the last value for repeated object keys. The supplied
parse_constant hook rejects those constants. The object_pairs_hook rejects
duplicate member names, including inside nested objects. Rejecting duplicates
is this client's deliberate ambiguity policy; JSON interoperability guidance
recommends unique names. The helper definitions are visible and supplied so
you can focus the build on response policy.
After parsing, require an object with items as a list containing only objects.
Optional next is absent, null or a nonempty string. Extra fields are preserved.
An empty items list succeeds; an empty body on a 200 fails JSON parsing. An
object containing items=[[]] is valid syntax with the wrong schema. These
cases deserve different answers from a timeout or a 404.
Construct one example for each failure code before coding: http-status,
content-type, encoding, json, schema. Then create a valid empty page
and a no-content result. The bugs challenge status precedence and element
validation; the build returns exactly ok, status, value and error.
Real clients additionally need input-size bounds and transport controls;
passing this deterministic decoder establishes its stated local behavior.
Read the Python json documentation for hooks and default extensions, and RFC 8259 for JSON types, encoding and interoperability guidance.