Warm-up · Activity 1 of 7
// B5.4 · ~25 min · Beginner
Saving structured data with JSON
After this lesson you can save lists and dictionaries to a JSON file and load them back, and predict what changes on the way: tuples, number keys and sets.
You will be able to
- Turn Python data into JSON text and back with json.dumps and json.loads, pretty-printed with indent
- Save data to a file and load it again with json.dump and json.load
- Predict what JSON changes or refuses: tuples become lists, keys become strings, sets fail
Predict · Activity 2 of 7
Predict before you read on: what does this print?
import json print(json.dumps({"name": "Ada", "active": True, "score": None}))Practice · Activity 3 of 7
Fill in the function that turns the JSON text into Python data.
import json text = '{"x": 1, "y": [2, 3]}' data = json.____(text) print(data["y"][1])json.(text)Practice · Activity 4 of 7
A dict with number keys goes to JSON and back. What does this print?
import json print(json.loads(json.dumps({1: "a", 2: "b"})))Practice · Activity 5 of 7
Match each Python value to what json.dumps writes for it.
Brain teaser · Activity 6 of 7
Brain teaser. A tuple goes to JSON and back. What does this print?
import json point = (3, 4) back = json.loads(json.dumps(point)) print(back == point, back)Apply · Activity 7 of 7
Mini-task. Save settings = {"theme": "dark", "font_size": 14} to settings.json with indent=2. Then load the file, add 2 to font_size, save it again, and load and print it once more. Open the file in the editor, or print its text, to see the indentation.
Check your work against this list
Build it yourself
Read the worked example, then write the exercises. Your code runs in your browser or on your computer and is never uploaded.
Worked example
An address book in a JSON file
A list of contact dicts is saved with json.dump and indent=2, the raw file is printed so you can see real JSON, and the file is loaded back with json.load. The last line uses json.dumps, which returns the text instead of writing it.
main.py
import json
contacts = [
{"name": "Ada", "phone": "+49 30 1234", "tags": ["work"]},
{"name": "Grace", "phone": None, "tags": []},
]
with open("contacts.json", "w", encoding="utf-8") as f:
json.dump(contacts, f, indent=2)
with open("contacts.json", encoding="utf-8") as f:
print(f.read())
with open("contacts.json", encoding="utf-8") as f:
loaded = json.load(f)
print(loaded == contacts, loaded[1]["phone"])
print(json.dumps({"total": 2, "ok": True}))
Run it with
python main.pyOutput
[
{
"name": "Ada",
"phone": "+49 30 1234",
"tags": [
"work"
]
},
{
"name": "Grace",
"phone": null,
"tags": []
}
]
True None
{"total": 2, "ok": true}- indent=2 puts every item on its own line, two more spaces per level; an empty list stays [].
- Python’s None is stored as null and comes back as None.
- loaded == contacts is True: lists, dicts, strings and None all survive the round trip unchanged.
- Without indent, dumps writes everything on one line.
Change it and run it
Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.
The first run downloads Python for your browser (up to 6.5 MB) and keeps it cached. Your code stays on your device.
Exercises
Exercise 1 of 2
Save and load any data
Write save_json(path, data), which writes data to the file as JSON indented with 2 spaces, replacing the old content, and load_json(path), which reads the file and returns the data. Use json.dump and json.load, and open the file with encoding="utf-8" both times.
Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.
The first run downloads Python for your browser (up to 6.5 MB) and keeps it cached. Your code stays on your device.
Hints
Hint 1
json.dump(data, f) writes into an open file; json.load(f) reads from one.
Hint 2
Pass indent=2 to json.dump to get one item per line with two spaces per level.
Hint 3
In save_json: json.dump(data, f, indent=2). In load_json: return json.load(f).
Show a solution
One way to solve it. Yours can look different and still pass the checks.
import json
def save_json(path: str, data: object) -> None:
with open(path, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2)
def load_json(path: str) -> object:
with open(path, encoding="utf-8") as f:
return json.load(f)
if __name__ == "__main__":
save_json("demo.json", {"tasks": ["write lesson"], "done": False})
print(load_json("demo.json"))
Run it on your computer
Install Python 3.14 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.
main.py
import json
def save_json(path: str, data: object) -> None:
with open(path, "w", encoding="utf-8") as f:
f.write(str(data))
def load_json(path: str) -> object:
with open(path, encoding="utf-8") as f:
return json.loads(f.read())
if __name__ == "__main__":
save_json("demo.json", {"tasks": ["write lesson"], "done": False})
print(load_json("demo.json"))
test_main.py
import json
from main import load_json, save_json
def test_round_trip():
"""A dict with a list, None and True comes back equal"""
data = {"tasks": ["a", "b"], "due": None, "done": True, "city": "München"}
save_json("t1.json", data)
got = load_json("t1.json")
assert got == data, f"load_json returned {got!r}, expected {data!r}"
def test_real_json():
"""The file holds JSON that other programs can read"""
save_json("t2.json", {"done": False})
with open("t2.json", encoding="utf-8") as f:
text = f.read()
try:
json.loads(text)
except ValueError:
assert False, f"the file is not valid JSON: {text!r}"
assert "false" in text, f"the file holds {text!r}: False must be written as false"
def test_indent():
"""The file is indented with 2 spaces"""
save_json("t3.json", {"a": 1})
with open("t3.json", encoding="utf-8") as f:
text = f.read()
assert text == '{\n "a": 1\n}', f"the file holds {text!r}, expected 2-space indentation"
On macOS and Linux, type python3 wherever these commands say python, as in the first lesson.
Run the program:
python main.pyRun the checks (needs learnrun.py in the same folder):
python learnrun.py testDownload learnrun.pyExercise 2 of 2
Who lives in this city?
contacts.json (second tab) holds a list of contacts, each a dict with name and city. Read a city with input(), load the file with json.load, and print the names of everyone in that city in alphabetical order, one per line. If nobody lives there, print nobody in and the city, for example nobody in Rome.
Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.
The first run downloads Python for your browser (up to 6.5 MB) and keeps it cached. Your code stays on your device.
Hints
Hint 1
with open("contacts.json", encoding="utf-8") as f: contacts = json.load(f) gives you a list of dicts.
Hint 2
Each contact is a dict, so contact["city"] and contact["name"] read its fields.
Hint 3
sorted(contact["name"] for contact in contacts if contact["city"] == city) gives the names in order; an empty list is false.
Show a solution
One way to solve it. Yours can look different and still pass the checks.
import json
city = input()
with open("contacts.json", encoding="utf-8") as f:
contacts = json.load(f)
names = sorted(contact["name"] for contact in contacts if contact["city"] == city)
if names:
for name in names:
print(name)
else:
print("nobody in", city)
Run it on your computer
Install Python 3.14 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.
main.py
import json
city = input()
# Load contacts.json and print the names of the contacts in this city.
print("nobody in", city)
test_main.py
from learnrun import run_main
def names_in(city):
return run_main(city + "\n").strip().splitlines()
def test_berlin():
"""Berlin lists Ada and Linus, sorted"""
got = names_in("Berlin")
assert got == ["Ada", "Linus"], f"for Berlin the program printed {got!r}"
def test_munich():
"""München lists one name"""
got = names_in("München")
assert got == ["Grace"], f"for München the program printed {got!r}"
def test_nobody():
"""A city with no contacts prints nobody in …"""
got = names_in("Rome")
assert got == ["nobody in Rome"], f"for Rome the program printed {got!r}"
contacts.json
[
{"name": "Linus", "city": "Berlin"},
{"name": "Grace", "city": "München"},
{"name": "Ada", "city": "Berlin"}
]
On macOS and Linux, type python3 wherever these commands say python, as in the first lesson.
Run the program:
python main.pyRun the checks (needs learnrun.py in the same folder):
python learnrun.py testDownload learnrun.pyCommon mistakes
Saving a set
import json
tags = {"work", "home"}
print(json.dumps(tags))
What Python prints
TypeError: Object of type set is not JSON serializableWhy, and the fix
JSON has no sets. Convert the set to a list before saving, ideally sorted so the file is the same every time: json.dumps(sorted(tags)). After loading, set(...) turns it back into a set.
Writing JSON with single quotes
import json
data = json.loads("{'name': 'Ada'}")
What Python prints
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)Why, and the fix
JSON strings and keys need double quotes; single quotes are Python, not JSON. Write the text as '{"name": "Ada"}', or better, let json.dumps produce it instead of typing JSON by hand.
Passing a file name to json.load
import json
data = json.load("data.json")
What Python prints
AttributeError: 'str' object has no attribute 'read'Why, and the fix
json.load expects an open file, and it calls .read() on whatever you pass. Open the file first: with open("data.json", encoding="utf-8") as f: data = json.load(f). json.loads is the one that takes a string, of JSON text, not a file name.
Python in the browser: Pyodide 314.0.7, MPL-2.0. Licence and source
Exit ticket
5 questions, no hints. Score 80% or more to complete the lesson.
Finish every activity above to unlock the exit ticket.