CodeRigor

FREE LESSON

JSON decoding, schemas & explicit errors

Process status, content type, UTF-8, JSON syntax and API shape in a deliberate order without hiding failures.

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.

Validate one boundary at a time
start / 3
Status precedes metadata, then UTF-8, JSON syntax and API schema. Stop at the first failed boundary.
Validate one boundary at a time
Status precedes metadata, then UTF-8, JSON syntax and API schema. Stop at the first failed boundary.

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.

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

Syntax success is not schema success
start / 2
Both examples parse as JSON; only the declared page object is usable for this API.
Syntax success is not schema success
Both examples parse as JSON; only the declared page object is usable for this API.

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.

Watch & read

Free lectures, talks and readings that cover this skill from another angle. All resources →

Focus on loads/dumps and the mapping between JSON and Python values. The lab adds status precedence, UTF-8, duplicate-member rejection and endpoint-specific schema checks; no network calls are graded.