Structured Data with JSON
Learning Objectives
- You can distinguish Python objects, JSON text, and open files.
- You can save and load nested data as one JSON document.
- You can explain why parsing a format does not validate an application’s records.
- You can choose CSV or JSON based on the information being represented.
CSV represents a flat table. A record can also contain a list or another record, as in Part 4. JSON is a text format that represents those nested relationships directly.
Python Values and JSON Text
Start with one small record:
record = {"ready": True, "note": None, "scores": [2, 5]}The same information in JSON is:
{"ready": true, "note": null, "scores": [2, 5]}JSON uses double quotes around strings, true and false for booleans, and null for the absence of a value. JSON is data, not Python source code. Use a parser to read it rather than trying to execute it.
Convert Between Values and Text
json.dumps(data) produces a JSON string. json.loads(text) parses a JSON string into Python values. Predict the types and final score before running this example:
The text is one string, even though it describes several values. Parsing constructs the dictionary and list that the program can access. A JSON document can also describe a list or a simple value at its outermost level; the result is not always a dictionary.
Trace these conversions before adding files:
Write and Load One Document
For an open file, use json.dump(data, file) and json.load(file). The versions ending in s work with strings; the versions without it work with files.
| Operation | Receives | Produces or changes |
|---|---|---|
dumps(data) | Python data | A JSON string |
loads(text) | A JSON string | Python data |
dump(data, file) | Python data and a writable file | Writes JSON to the file |
load(file) | A readable file | Python data |
This complete example saves two session records and reads them back during the same Run. Predict whether the loaded minutes need int(...) as they did with CSV.
indent=2 formats the output for people; it does not change the represented data. Use string keys in saved dictionaries. JSON object keys are strings, so numeric Python keys should not be expected to remain numeric after a round trip.
Write the whole collection once. Several consecutive dump calls do not produce one valid JSON document. To save several records, put them in a list and save that list.
Try sessions = [] in the editor. Remove the final assertion about the first item, because an empty list has no item at index zero. The equality check should still pass.
Now put the file operations into two functions. This task assumes valid session records; the next chapter adds validation.
Keep session records
0 / 20 points
Write save_sessions(path, sessions) and load_sessions(path). sessions is a list of dictionaries with exactly two keys: subject (a string) and minutes (a positive integer), for example {"subject": "Math", "minutes": 20}. Save the list itself as one UTF-8 JSON document, without an outer dictionary. Replace previous contents and return None. Loading returns the list read from that document. Empty lists are valid.
Do not print or modify the supplied records. Loading creates new data, independent of the original records. File and JSON parsing errors propagate. This task assumes loaded data has the specified shape; application validation comes next. JSON indentation and escaping style are not graded.
Work locally
Download this exercise with the Aalto OpenCS IDE plugin, or use Download starter code below and extract the ZIP into its own folder inside programming-practice. Open that exercise folder in your IDE. Keep each exercise in a separate folder.
| File | Role |
|---|---|
main.py | Write save_sessions(path, sessions) and load_sessions(path) here. |
check.py | Supplied checks that save and reload JSON; extend them. |
Save your changes, then open the IDE terminal in this exercise folder: dir on Windows or ls on macOS/Linux should show the files above. Relative paths in the checks start from this folder. Run:
| System | Run the checks |
|---|---|
| Windows | py check.py |
| macOS/Linux | python3 check.py |
Use python instead if that is your working Python command. Successful supplied checks print Checks passed.. Read any failed assertion or traceback, fix your code, and rerun the checks. You may extend check.py; use separate data files for extra cases so the supplied samples retain their contents.
The checks save a session whose subject is "Mäki" and compare the loaded records with the originals. They then save [], so practice_sessions.json contains an empty JSON list after the checks. The file remains available when the process ends.
main.py defines functions; running it alone produces no output. check.py imports and calls those functions. Keep file operations inside the functions, with test calls in check.py, so importing main.py does not read or write data files or ask for input.
Submit your solution
Save your files. Submit using Submit Current Exercise in the IDE plugin, review the file list, and confirm Submit. Alternatively, create a ZIP from the exercise folder:
| System | Command |
|---|---|
| Windows | py -m zipfile -c submission.zip main.py check.py |
| macOS/Linux | python3 -m zipfile -c submission.zip main.py check.py |
Upload submission.zip below and press Submit. The listed files must be at the archive’s top level, without an enclosing folder. Include the supplied support and sample files; leave out generated outputs, .venv, and __pycache__. Read the grading feedback after submitting. The grader also uses its own inputs; passing the sample checks alone does not establish the whole contract.
Restored Data Has Its Own Objects
Loading creates new dictionaries and lists. It does not reconnect the loaded records to the original in-memory objects.
Predict both lists before running the example. This is different from the shallow copies in Part 4.
JSON preserves the supported values and their structure, not the shared-object relationships of the original program.
An empty list also has a representation: []. A file containing those two characters is valid JSON. A file with no characters is not a JSON document and cannot be loaded as an empty list.
Related CSV Files or One Nested JSON Document?
The same information can have more than one representation. Suppose we want word pairs and their practice attempts.
One CSV file can hold the vocabulary:
id,finnish,english1,yksi,one2,kaksi,twoA second CSV file can hold attempts, referring to a pair by its identifier:
word_id,direction,response,correct1,fi-en,one,12,fi-en,thu,01,en-fi,yksi,1To connect them, first read both files, then find each attempt’s word pair. The dictionaries below are what the CSV readers would produce. Predict which Finnish word is printed for each attempt.
Change the second attempt’s identifier to "99". The lookup fails because there is no matching word. A file reader can parse both files successfully while the relationship between them is invalid. A complete loader must check that identifiers are unique and references exist before using them.
A JSON document can instead keep each word with its attempts:
[ { "finnish": "yksi", "english": "one", "attempts": [ {"direction": "fi-en", "response": "one", "correct": true}, {"direction": "en-fi", "response": "yksi", "correct": true} ] }]Here the nesting expresses which word the attempts belong to. There is no second-file lookup for that relationship. Adding another attempt normally means loading, updating, and rewriting the document; appending a separate JSON object would not preserve this one-document format.
| Design | Useful property | Responsibility |
|---|---|---|
| Related CSV tables | Each table can be inspected and processed separately | Match identifiers and keep references meaningful |
| Nested JSON document | One document directly represents the nested collection | Navigate the structure and save a complete valid document |
| History rows containing the asked words | Old attempts describe themselves | Decide how they relate to today’s vocabulary |
Keep Historical Questions Understandable
Word Practice uses the third design. Its history records include the displayed word, expected translation, actual response, direction, and correctness. They can still be read after a word is removed from the vocabulary.
This repetition is deliberate: the row records what was asked at the time. Looking up only today’s translation could change the apparent meaning of an earlier answer. The initial game does not need identifiers; a later version could add stable identifiers while retaining historical question details.
CSV is convenient for this game’s two flat tables. JSON would be useful for exporting a complete vocabulary-and-history collection in one nested document. The file format and the decision to keep information in one or several files are related choices, but neither dictates the other.
Check Your Understanding
- What differs between a Python dictionary and a JSON string describing it?
- Which pair of operations uses open files, and which pair uses strings?
- Why is
[]different from an empty file? - Does loading a saved list preserve references to the original in-memory records?
- What must a program check when an attempt refers to a word by identifier?
- When would you choose linked CSV tables, and when a nested JSON document? Give one use and one responsibility for each design.