PythonBeginner 13 min Lesson 25 of 30

Day 25 — JSON and HTTP

Understand the request and response cycle, common status codes and methods, and how JSON converts between Python objects and text.

Python · Lesson 25 of 30
0/30 done(0%)

What is it? #

HTTP is the set of rules browsers and servers use to talk. A client sends a request — a method, a path, some headers and maybe a body. The server replies with a status code, headers and usually a body.

The methods carry meaning. GET reads, POST creates, PUT and PATCH update, DELETE removes. Following those conventions means anyone can guess what your API does.

Status codes are the server's summary. 2xx worked, 3xx means go elsewhere, 4xx means the client made a mistake, 5xx means the server did.

JSON is the usual format for the body. It is plain text with a simple structure that maps almost exactly onto Python dictionaries, lists, strings, numbers, booleans and null.

Think of it like this #

HTTP is the postal system. The method is what kind of item you are sending, the path is the address, the headers are the details written on the envelope, and the body is what is inside. The status code is the delivery receipt: delivered, wrong address, or the sorting office caught fire.

Simple example #

You receive a JSON payload from a web form, convert it into Python objects, validate it, and send back a JSON response with an appropriate status code. Along the way you handle a date, which JSON does not support natively.

Code #

PYTHON
import json
from datetime import date, datetime

# Text coming in from a request body
raw = '{"customer": "Ravi", "amount": 1500, "paid": true, "coupon": null}'

data = json.loads(raw)             # JSON text -> Python objects
print(type(data), data["amount"])  # <class 'dict'> 1500
print(data["paid"], data["coupon"])  # True None

# Python objects -> JSON text
payload = {
    "id": "A-1",
    "items": ["pen", "book"],
    "total": 999.5,
    "shipped": False,
}
print(json.dumps(payload))
print(json.dumps(payload, indent=2, sort_keys=True))

# Dates are not valid JSON — convert them
record = {"id": "A-1", "placed_on": date.today()}

def encode(value):
    if isinstance(value, (date, datetime)):
        return value.isoformat()
    raise TypeError(f"Cannot serialise {type(value).__name__}")

print(json.dumps(record, default=encode))   # {"id": "A-1", "placed_on": "2026-09-22"}

# Handling malformed input
try:
    json.loads("{not json}")
except json.JSONDecodeError as exc:
    print("Bad payload:", exc.msg)
TEXT
Common status codes you will actually use

200 OK               request succeeded
201 Created          a new resource exists now
204 No Content       success, nothing to send back
400 Bad Request      the client sent something invalid
401 Unauthorized     no valid credentials
403 Forbidden        authenticated, but not allowed
404 Not Found        no such resource
409 Conflict         clashes with current state, e.g. duplicate
422 Unprocessable    syntactically fine, semantically wrong
429 Too Many Requests  rate limited
500 Internal Error   the server broke
503 Service Unavailable  temporarily down or overloaded

How it works #

json.loads() parses text into Python objects. Note the type mapping: JSON true becomes True, null becomes None, objects become dictionaries and arrays become lists.

json.dumps() goes the other way, producing text. indent=2 makes it human-readable for logs and debugging, and you would leave it out for real responses to save bytes.

JSON has no date type. Passing a date object straight to dumps raises TypeError. The default= hook is called for anything JSON does not understand, which is where you convert dates to ISO strings — 2026-09-22 — the format everything else can parse.

json.JSONDecodeError is what you get from malformed input. Any endpoint that accepts a body should handle it and respond with 400 rather than letting it become a 500.

The status code list is worth memorising because it communicates intent. Returning 200 with {"error": "not found"} in the body forces every client to parse the body to know what happened. Returning 404 tells them immediately — and lets caches, proxies and monitoring do the right thing.

Real-world use #

Every API you build or call runs on these rules. Getting status codes right is not pedantry: load balancers retry on 502, browsers cache 301s permanently, and monitoring dashboards count 5xx as your error rate.

The 4xx versus 5xx split matters in practice. A 4xx says the caller must change something; a 5xx says you must. Mislabelling validation failures as 500s makes your error dashboards useless.

JSON is the default because it is readable, language-neutral and maps onto basic data types everywhere. Its limits — no dates, no comments, no binary, floats only — are the reason teams settle on conventions like ISO-8601 for dates and base64 for small binary payloads.

For large or high-volume payloads, other formats exist, but almost every web project starts and stays with JSON.

Common mistakes #

  • Returning 200 for errors and putting the real status in the body.
  • Forgetting that JSON has no date type, then shipping inconsistent formats.
  • Letting a JSONDecodeError become a 500 instead of a 400.
  • Using POST for everything, including reads, which breaks caching and readability.
  • Trusting JSON input without validation. A parsed dictionary is not a validated one.

Practice #

Take the JSON string '{"user": {"name": "Anita", "roles": ["admin", "editor"]}, "active": true}'. Parse it, print the user’s name and the number of roles, add a last_login field with today’s date, and print it back as formatted JSON with the date as an ISO string. Then feed it invalid JSON and handle the error.

Quick quiz

  1. 1. What does `json.loads()` do?

  2. 2. What does JSON `null` become in Python?

  3. 3. Which status code means the client sent something invalid?

  4. 4. Why can you not serialise a `date` object directly to JSON?

  5. 5. What is the practical difference between 4xx and 5xx?

Summary

  • HTTP requests carry a method, path, headers and optional body; responses carry a status code and body.
  • Methods and status codes communicate intent — use them honestly.
  • `json.loads` parses text into Python objects; `json.dumps` produces text.
  • JSON has no date type, so use ISO-8601 strings.
  • Handle malformed JSON as a 400, not a crash.