Talking to the Outside World
JSON looks almost exactly like a Python dictionary, right up until you notice the differences. Convert one into the other in both directions, and find the two places the conversion is not quite what you expected.
Read first: Looking Things Up Instead of Counting Along
JSON looks almost exactly like a Python dictionary, right up until you notice the differences. Convert one into the other in both directions, and find the two places the conversion is not quite what you expected.
The format everything on the internet already speaks
JSON — JavaScript Object Notation, despite the name — is the text format almost every web API sends and receives data in. It is not Python, and not JavaScript; it is a shared, language-neutral shape that both happen to read easily.
That neutrality is the entire reason it won: a Python program, a JavaScript program in a browser, and a service written in a language neither of them uses can all agree on what {"name": "Ada", "age": 36} means, without any of them knowing or caring what the others are written in.
{
"name": "Ada",
"age": 36,
"active": true,
"manager": null
}json.loads(text) turns this string into a real dict — and along the way, true becomes True, and null becomes None.
The exact mapping between JSON and Python
The two look alike because most JSON types map onto a Python type directly — but "most" is doing real work in that sentence, and the exceptions are exactly where a hand-written JSON string breaks.
Becomes a Python dict, key order preserved.
Becomes a Python list.
Becomes a Python str, always double-quoted in JSON — single quotes are not legal JSON.
Becomes an int if it has no decimal point, otherwise a float — JSON itself has only one number type.
Become Python's True / False, capitalised.
Becomes Python's None.
The mapping runs in both directions — json.dumps turns a Python value back into JSON text using the same table read the other way, a dict becoming an object, a None becoming null. A Python tuple has no row in this table at all; json.dumps converts one to a JSON array anyway, and once it comes back through json.loads it is a plain list, not a tuple — the round trip does not always return the exact type you started with.
Loading JSON turns it into ordinary Python values
$ >>> import json$ >>> text = '{"name": "Ada", "age": 36}'$ >>> record = json.loads(text)$ >>> record$ {'name': 'Ada', 'age': 36}$ >>> record["age"]$ 36json.loads(text) parses a JSON string into an ordinary Python dict — after this line, it behaves exactly like any dictionary built by hand, with no trace of where it came from. json.dumps(record) does the reverse, turning a dict back into a JSON string.
json.loads takes a string you already have in memory. json.load — no s — takes an open file object and reads directly from it, which matters because it is the difference between json.load(f) and the far more common mistake, json.loads(f), which fails: loads expects text, and a file object is not text, it is something you read text from.
$ >>> with open("record.json") as f:$ ... record = json.load(f)$ ...$ >>> record["name"]$ 'Ada'Where the two formats quietly disagree
JSON has no distinct type for a whole number versus a decimal the way some languages do, and more importantly: JSON spells its boolean and null values true, false, and null, lowercase — Python spells the same values True, False, and None, capitalised. The json module translates between them automatically in both directions, but writing raw JSON by hand with True instead of true is a common, easy-to-miss source of a parse error.
Making a request and actually checking what came back
requests.get(url).json() parses whatever the server sent as JSON, without asking first whether the request actually succeeded. A 404, a 500, or a server that is simply down still returns a response — often with a body that is not JSON at all — and calling .json() on it fails with a JSONDecodeError that has nothing to do with JSON being the real problem.
import requests response = requests.get("https://api.github.com/users/octocat", timeout=5) if response.status_code == 200: data = response.json()else: print(f"Request failed: {response.status_code}")Checking response.status_code before touching .json() catches the failure at the point it actually happened, with a message that says what went wrong. response.raise_for_status() does the same check the other way around — it raises an exception immediately on any error status, which suits a script that should stop rather than continue on bad data.
Timeouts, rate limits, and the key you should never commit
A request with no timeout argument waits forever if the server never responds — not slowly, forever, hanging the entire program on a single stalled connection. requests.get(url, timeout=5) gives up after five seconds and raises a requests.exceptions.Timeout instead, which is at least something a program can catch and react to.
A 429 status code means the API is rate-limiting you — you have called it too many times too quickly, and it is refusing to answer for a while. Many APIs send a Retry-After header alongside it stating exactly how many seconds to wait; reading that header and pausing before trying again is the polite, working response, and hammering the endpoint immediately again is the one move guaranteed to make the block last longer.
Key takeaways
- JSON is a text format, not Python or JavaScript specifically — it's the shared shape most web APIs speak.
- JSON's object, array, string, number, true/false, and null map onto Python's dict, list, str, int or float, True/False, and None, in that order.
- json.loads parses a string you already have; json.load reads directly from an open file — mixing them up is a common, silent bug.
- Checking response.status_code before calling .json() catches a failed request where it actually failed, instead of as a confusing JSONDecodeError later.
- A timeout keeps one dead connection from hanging your whole program forever, and an API key belongs in an environment variable, never typed into a file you commit.
Quick check
Answer these to unlock the next chapter — 3 of 4 to pass. You can retake it anytime.
Answer every question to check.
Make a free account to read on
Every chapter is free — an account is how your progress, XP, and streak follow you from your laptop to your phone, and how you show up on the leaderboard. No payment, no trial.