LESSON 144 · DEEPER PYTHON30 min

Validate JSON from HTTP Responses in Python

Validate an HTTP-like response's status, JSON syntax, and expected field types before using its data.

Example check: Worked and guided code samples are checked against CPython 3.13. Practice uses Pyodide 0.29.5 (Python 3.13) in your browser. How we check lessons

Video lesson: Validate JSON from HTTP Responses in Python

The preview is stored on this site. YouTube loads only when you press Play. Watch on YouTube

Read the video transcript

Select a timestamp to watch that moment on YouTube.

  1. 00:00

    A client receives status 503 and a body containing an HTML busy page. What happens if we call json.loads and then index a name? Before running the example, separate the three things a client needs to know: did the request succeed, is the body syntactically valid JSON, and does the decoded value match the expected user schema? A parser only answers the second question. Our lesson uses a local pair of status and body rather than making a real HTTP request, so every run in the browser is deterministic. The function we will build returns a narrow dictionary with integer id and nonempty name.

  2. 00:42

    We will reject any response that cannot satisfy that explicit contract. An HTTP response has several independent layers. A successful-looking body is not enough: check the status, parse the body as JSON, then validate the shape of the parsed data. For this browser lesson, a response is simply a local (status, body) pair. No URL is fetched and no network access is needed. The expected record is an object with integer id and nonempty string name. The function returns a smaller validated dictionary, leaving unrelated fields out. This makes the contract visible and keeps later code from relying on an unchecked

  3. 01:23

    response. The broken program immediately decodes the 503 HTML body. Python raises JSONDecodeError because the body is not JSON. That is a useful traceback, but it is not the first fact the caller should inspect. The server already reported a failure status. In a different case, an error response might contain syntactically valid JSON, such as an object with an error message. Blind parsing would then succeed but still not produce the user record we need. Checking the status before decoding keeps those situations clear. Avoid treating every failure as a generic exception to catch and ignore; an invalid

  4. 02:07

    response should remain visible to the caller with a meaningful reason. The broken example calls json.loads on every body. A local 503 response contains an HTML error page, so decoding raises JSONDecodeError. The decoder did its job; the caller skipped the status check. A different server might return valid JSON describing an error, and parsing would succeed while the business data was still unavailable. Inspect the status and a short representation of the body before deciding to parse. Avoid logging a real response body indiscriminately when it might contain

  5. 02:47

    private data; this lesson uses harmless fake text. The diagnosis prints each local status and a short body preview. It also decodes a valid JSON array under status 200 and reports that the Python value is a list. A successful status plus valid JSON still does not guarantee a user object. This is an important boundary in API work: text syntax and application schema are different layers. json.loads can return a dict, list, number, string, boolean, or None. We have to check the type before accessing keys. After that, we must validate each field.

  6. 03:28

    An id field with true is especially deceptive because bool is an int subclass in Python; exact type checking is appropriate for this particular identifier contract. A 200 status does not guarantee a usable payload. The body might be malformed JSON, a list instead of an object, or an object whose id is a string or boolean. json.loads only converts syntax into Python values; it does not know the application's schema. Use type(id_value) is int when a boolean must not count as an integer, and require name to be a nonempty string after stripping whitespace.

  7. 04:08

    Raise ValueError for an invalid status, syntax, shape, or field value so the caller sees a clear boundary failure. Catch JSONDecodeError specifically and attach a concise message. The corrected function checks for status 200 first. It catches JSONDecodeError specifically and raises ValueError with a concise invalid-JSON message. Then it requires an object and examines its fields. The id must be an actual integer, and the name must be a string containing visible characters after stripping surrounding whitespace. Only then does the function return the checked id and stripped name.

  8. 04:48

    We ignore unrelated fields rather than passing through an unchecked object. This result acts like a clean boundary: downstream code can rely on exactly the two fields it needs. The fixed run accepts a normal local response and prints the validated record. Trace the validator in order. A status other than 200 fails before body decoding, which avoids treating an HTML error page as a user object. A 200 response reaches json.loads; only syntax errors from that decoder are translated into a concise ValueError. A valid decoded value then faces a separate object check.

  9. 05:30

    Each field is validated according to this application's contract, including exact integer type so True is not accepted as an identifier. Finally, name is stripped and the function returns only the checked fields. Each failure point tells us which layer failed. For production, the caller can catch ValueError at an appropriate boundary and decide how to present the error without pretending the record was usable. Finally, run a no-content 204 response, malformed JSON under 200, and a body with a boolean id. All three are rejected for different reasons. The code does not fetch data, so you can modify each case and rerun without network

  10. 06:11

    variability. In a real client, additional checks might cover content type, redirects, authorization, limits on body size, and API-specific error structures. Those are future concerns, while today's task is a precise response contract. Complete the practical checker, then explain in the quiz why a valid JSON document is not automatically a successful or usable HTTP response. That reasoning will be reused in the offline client project later in the course. The verification run tries a normal record, a 503 error, and a valid JSON body with an invalid id.

  11. 06:53

    A 204 with no body also cannot satisfy this record contract. In a real HTTP client, redirects, content types, authentication, and timeouts add more concerns; the code here isolates response validation. Treat untrusted external data as a value to be checked before it reaches downstream computation. This is especially helpful when API providers change fields or return a structured error in place of a success record.

Understand the concept

An HTTP response has several independent layers. A successful-looking body is not enough: check the status, parse the body as JSON, then validate the shape of the parsed data. For this browser lesson, a response is simply a local (status, body) pair. No URL is fetched and no network access is needed. The expected record is an object with integer id and nonempty string name. The function returns a smaller validated dictionary, leaving unrelated fields out. This makes the contract visible and keeps later code from relying on an unchecked response.

The broken example calls json.loads on every body. A local 503 response contains an HTML error page, so decoding raises JSONDecodeError. The decoder did its job; the caller skipped the status check. A different server might return valid JSON describing an error, and parsing would succeed while the business data was still unavailable. Inspect the status and a short representation of the body before deciding to parse. Avoid logging a real response body indiscriminately when it might contain private data; this lesson uses harmless fake text.

A 200 status does not guarantee a usable payload. The body might be malformed JSON, a list instead of an object, or an object whose id is a string or boolean. json.loads only converts syntax into Python values; it does not know the application's schema. Use type(id_value) is int when a boolean must not count as an integer, and require name to be a nonempty string after stripping whitespace. Raise ValueError for an invalid status, syntax, shape, or field value so the caller sees a clear boundary failure. Catch JSONDecodeError specifically and attach a concise message.

The verification run tries a normal record, a 503 error, and a valid JSON body with an invalid id. A 204 with no body also cannot satisfy this record contract. In a real HTTP client, redirects, content types, authentication, and timeouts add more concerns; the code here isolates response validation. Treat untrusted external data as a value to be checked before it reaches downstream computation. This is especially helpful when API providers change fields or return a structured error in place of a success record.

  • Check status before decoding
  • Parse JSON syntax separately from schema
  • Reject bool when an exact integer is required
  • Return a validated narrow record

See it step by step

Read the code, predict the output, then compare it with the result.

01. Inspect a local error response

PYTHON
status, body = 503, '<html>busy</html>'
print(status, repr(body[:20]))
EXPECTED OUTPUT
503 '<html>busy</html>'

Status explains why parsing the HTML body as a user record would be wrong.

02. JSON syntax is not a schema

PYTHON
import json
value = json.loads('[1, 2]')
print(type(value).__name__, value)
EXPECTED OUTPUT
list [1, 2]

A valid JSON array does not match the expected user object.

03. Reject a boolean identifier

PYTHON
import json
value = json.loads('{"id": true, "name": "Ada"}')
print(isinstance(value['id'], int), type(value['id']) is int)
EXPECTED OUTPUT
True False

Exact integer checking rejects booleans even though isinstance sees them as integers.

A closer look

Follow the reasoning, inspect each result, then try the suggested changes in the console below.

01 / 03

Read status before content

An HTTP response has several independent layers. A successful-looking body is not enough: check the status, parse the body as JSON, then validate the shape of the parsed data. For this browser lesson, a response is simply a local (status, body) pair. No URL is fetched and no network access is needed. The expected record is an object with integer id and nonempty string name. The function returns a smaller validated dictionary, leaving unrelated fields out. This makes the contract visible and keeps later code from relying on an unchecked response.

A local fake response keeps the exercise deterministic. First inspect the status and a short body preview. A production client might log only a safe code or request identifier; avoid exposing full sensitive response bodies in logs.

PYTHON
status, body = 503, '<html>busy</html>'
print(status, repr(body[:20]))
EXPECTED OUTPUT
503 '<html>busy</html>'
Follow the reasoning
  1. Inspect status 503.
  2. Avoid decoding an error page as a record.
02 / 03

Separate syntax from shape

The broken example calls json.loads on every body. A local 503 response contains an HTML error page, so decoding raises JSONDecodeError. The decoder did its job; the caller skipped the status check. A different server might return valid JSON describing an error, and parsing would succeed while the business data was still unavailable. Inspect the status and a short representation of the body before deciding to parse. Avoid logging a real response body indiscriminately when it might contain private data; this lesson uses harmless fake text.

Run json.loads on a valid array and observe the returned Python list. A parser cannot infer that your application wanted an object. Add an explicit type check before field access, and distinguish malformed syntax from valid but wrong-shaped data.

PYTHON
import json
value = json.loads('[1, 2]')
print(type(value).__name__, value)
EXPECTED OUTPUT
list [1, 2]
Follow the reasoning
  1. Decode a valid JSON array.
  2. Compare syntax with expected object shape.
03 / 03

Validate exact field contracts

A 200 status does not guarantee a usable payload. The body might be malformed JSON, a list instead of an object, or an object whose id is a string or boolean. json.loads only converts syntax into Python values; it does not know the application's schema. Use type(id_value) is int when a boolean must not count as an integer, and require name to be a nonempty string after stripping whitespace. Raise ValueError for an invalid status, syntax, shape, or field value so the caller sees a clear boundary failure. Catch JSONDecodeError specifically and attach a concise message.

Test missing, blank, wrong-type, and boolean identifiers. Strip a name only after confirming it is a string. Return a small checked dictionary, which becomes a trusted boundary for subsequent lessons and projects.

PYTHON
import json
value = json.loads('{"id": true, "name": "Ada"}')
print(isinstance(value['id'], int), type(value['id']) is int)
EXPECTED OUTPUT
True False
Follow the reasoning
  1. Check id and name types.
  2. Try a boolean id and whitespace name.

Try it in Python

Edit the example and run it. Python starts in your browser the first time you click Run.

Python console

Ready to run
Need input()? Add one value per line
Ctrl / ⌘ + Enter to run
OUTPUT
Your output appears here.
Your turnImplement parse_user_response(status, body). Accept only status 200 and a JSON object with integer id (not bool) and nonempty string name; return {'id': id, 'name': stripped_name}. Raise ValueError for every invalid response.
Need a hint?

Check status first, catch json.JSONDecodeError, verify dict and exact id type, then strip and validate name.

Complete the task and select Check task to verify your code.

    Quick quiz

    Three questions. You can change your answers and try again.

    01.Does valid JSON prove an HTTP request succeeded?

    02.What does json.loads validate?

    03.Why use type(value) is int for id here?

    Typical mistakes

    Everyone meets these errors. See what causes them and how to fix them.

    Decoding every response

    COMMON MISTAKE
    payload = json.loads(body) before checking status
    FIX
    if status != 200: raise ValueError('unexpected status')

    What happens: An HTML error page raises JSONDecodeError or a JSON error is treated as data.
    Transport status and payload syntax are separate checks.

    Assuming decoded data has the right shape

    COMMON MISTAKE
    return payload['name']
    FIX
    validate dict, id type, and nonempty name

    What happens: A list or missing field crashes later or produces misleading data.
    JSON parsing does not enforce the application's record schema.

    Browse this lesson’s problem fixes

    Sources and further reading