Working with JSON
The data format of the web, and the one module that turns Python values into it and back again.
Text that looks like Python
JSON is a way of writing lists and dictionaries as text. It looks almost exactly like Python for the same values — {"name": "Ada", "languages": ["English", "French"]} — and that likeness is why it is the format config files, APIs and saved data all speak.
The json module turns your values into that text and back: json.dumps(data) gives a string, and json.loads(text) gives your values again. The s on the end of each name is for "string".
There and back
import json
recipe = {"name": "Flatbread", "minutes": 25, "steps": ["mix", "rest", "bake"]}
text = json.dumps(recipe, indent=2)
print(text)
loaded = json.loads(text)
print(loaded["name"])Files and the world outside
With a file, the pair is json.dump(data, file) and json.load(file) — the same conversion, aimed at a file instead of a string. That is how a program keeps its settings, its notes or its saved games between runs.
The same two functions are what you use with every API you will ever call: a request comes back as JSON text, loads turns it into dictionaries and lists, and your program works with it like any other data.
Two rules JSON keeps
JSON has no comments, and its strings always use double quotes. That is the whole difference you will notice: a Python dictionary written with single quotes is not valid JSON text, which is exactly why the module writes it for you.
indent=2 makes the text readable for a person; without it you get one long line, which is what machines prefer.
Save the recipe as JSON
Turn the dictionary into readable JSON text, save it to recipe.json, then read the file back and print the recipe’s name from the loaded data.
The text is the boundary
A dictionary in memory and a JSON document are different things that happen to look alike. The dictionary can hold a set, a function or a date; the document can hold only the six JSON types. dumps is the crossing point, and everything that cannot cross has to be converted first.
That is why the module raises TypeError instead of inventing a representation. A silent guess would produce a file that loads into something different from what you saved, and the difference would surface much later. A loud refusal at the boundary is the kinder behaviour.
Reading JSON you did not write
Text from an API or a file may be missing keys, hold null, or nest differently than you expected. loads only checks that the text is valid JSON; it does not check that the shape matches your program. The first lookup of a missing key raises KeyError, which is the moment the assumption becomes visible.
Use .get with a default for values that may be absent, and check the type before using it. A number that arrives as text will fail when you add to it, and the error will point at the arithmetic rather than at the document that supplied the string.
When a value refuses to convert — a set, a date, a custom object — the module says so plainly. Saving those means turning them into something JSON has, like a string or a list.
JSON has fewer types than Python
JSON understands objects, arrays, strings, numbers, booleans and null — and nothing else. A Python tuple becomes an array, a set cannot be written at all, and a date is not a JSON type. Converting is therefore lossy at the edges: (1, 2) comes back as [1, 2], and True comes back as true, which Python reads as True again.
The module handles the common shapes for you. When a value has no JSON equivalent, it raises TypeError rather than guessing; the fix is to convert that value yourself, usually with str(...) or by writing its parts as a dictionary.
The shape survives, the types may not
A round trip preserves the shape — dictionaries stay dictionaries, lists stay lists, nesting stays nested. It does not preserve Python types that JSON lacks: a tuple returns as a list, and an integer key in a dictionary returns as text.
That last one catches people out: {1: "one"} becomes {"1": "one"}. If the key matters, convert it back with int(key) after loading, or store the number as a value instead.
Three JSON mistakes: single-quoted Python text is not JSON, so json.loads("{'name': 'Ada'}") fails; a trailing comma is allowed in Python but not in JSON; and json.dump writes to a file while json.dumps returns a string — swapping them sends a file object where text was expected, or the reverse.
Try these next
- Dump a list of three names with and without
indent=2and compare the text. - Load a JSON string containing
true,falseandnull, and print the Python values and their types. - Try to dump a set, read the error, then convert it to a list and dump it again.
Settings with a default
Save the settings to settings.json as readable JSON, load them back, and print the theme. Then add a language key to the dictionary and run again — the fallback should stop being used.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What does json.dumps(data) give back?
