MGU Computer Science Series — Programming with Python
Part VII: Capstone and Extension
JSON · Structured Data · Files · Standard Library
Chapter 31
31
MGU Computer Science Series  ·  Programming with Python  ·  Library Textbook Edition

Chapter 31:
Working with
JSON Data

Reading, writing, and using structured data — converting between JSON text and Python objects, handling nested records, and building programs that persist information across sessions

■ Extension Chapter ■

■ 12 Sections  •  Follows Chapter 30 ■

Requires: Chapter 19 (Nested Data), Chapter 21 (Files), Chapter 23 (Exceptions)

Chapter Introduction and Learning Objectives

Chapter Purpose

Chapter 30 guided you through building and documenting a complete Python project. This chapter extends your practical skills by introducing one of the most widely used data formats in modern programming: JSON. You already know how to read and write plain text files and CSV table data. JSON gives you a third option — one that handles structured, nested, real-world data far more naturally than a flat table can. By the end of this chapter you will be able to read JSON files, write JSON files, convert between Python data structures and JSON text, and design small programs that store and retrieve structured records.

Every program you have written so far stores data in one of three places: in variables while the program runs, in plain text files, or in CSV files. Variables disappear when the program stops. Plain text files store unstructured prose. CSV files store flat tables. None of these is ideal when your data has layers — a student record with a name, a list of courses, and a nested address dictionary. JSON solves exactly that problem.

JSON is a standard, human-readable format that maps so naturally onto Python's own data types that the translation between them is nearly invisible. It is used everywhere: web APIs, configuration files, database exports, app preferences, and structured logging. Learning to work with JSON is a practical skill you will use in almost every serious Python project.

12Sections
10Objectives
13Key Terms
4Functions

Learning Objectives

By the end of this chapter, you should be able to:

  1. 1Explain what JSON is, what value types it supports, and why it is widely used.
  2. 2Map JSON types to their Python equivalents, including the boolean and null translations.
  3. 3Import the json module and identify the four main functions it provides.
  4. 4Use json.loads() to parse a JSON string into a Python object.
  5. 5Use json.dumps() to convert a Python object to a JSON string, with and without the indent parameter.
  6. 6Use json.load() to read JSON data from a file.
  7. 7Use json.dump() to write Python data to a JSON file.
  8. 8Apply the read-modify-write pattern to persist program state across sessions.
  9. 9Handle json.JSONDecodeError and FileNotFoundError in programs that work with JSON files.
  10. 10Choose between plain text, CSV, and JSON based on the shape of the data.

Sections at a Glance

Sections 31.1 – 31.3
What JSON Is, Its Types, and How to Import It
Format · Value Types · Python Equivalents · json Module

What JSON Is and Why It Matters

Every program you have written so far stores data in one of three places: in variables while the program runs, in plain text files, or in CSV files. Variables disappear when the program stops. Plain text files store unstructured prose. CSV files store flat tables with rows and columns. None of these is ideal when you want to save something like this:

The problem with flat formats
A student record that does not fit neatly in a CSV row
  • The recordA name, a student ID, a list of course grades, an address with its own sub-fields (city, state, zip), and an enrollment status flag.
  • CSV attemptYou could try to cram these into a flat row, but you would need to invent your own conventions for encoding the list of grades, separating the address fields, and reading them back correctly.
  • The better wayUse a format designed for structured, nested data — one with a standard that other tools already understand.

JSON solves this problem. JSON stands for JavaScript Object Notation. Despite the name, JSON has nothing to do with JavaScript for your purposes. It is simply a standard text format for representing structured data — strings, numbers, lists, dictionaries, booleans, and null values — in a way that both humans and programs can read.

JSON looks like this:

{
  "name": "Adriana Sousa",
  "student_id": "MGU-2041",
  "gpa": 3.7,
  "enrolled": true,
  "courses": ["CS101", "CS201", "MATH150"],
  "address": {
    "city": "Lakeview",
    "state": "OR",
    "zip": "97401"
  }
}

If you have worked through Chapter 19 on nested data structures, this should look familiar. It resembles a Python dictionary containing strings, a number, a boolean, a list, and another dictionary. That resemblance is not accidental. Python's json module converts between JSON text and Python objects so smoothly that the translation is nearly invisible.

Where JSON Appears

Web APIs
Talking to Web Services
When your program asks a web service for weather data, stock prices, or search results, the response almost always arrives as JSON.
Configuration
Settings Files
Many applications store settings in a JSON file so that preferences survive between sessions without being hardcoded in the source.
Databases
Data Export and Import
Modern databases support JSON columns, and most export tools offer JSON as an output format for transferring records.
Logging
Structured Event Records
Applications log structured events — timestamps, error codes, user IDs — in JSON so that automated tools can parse and analyze the logs later.
Study Note

Learning to work with JSON means your Python programs can communicate with the real world. Nearly every web API and most modern data pipelines speak JSON. The four functions you will learn in this chapter — json.loads(), json.dumps(), json.load(), and json.dump() — are among the most practically useful things in the standard library.

JSON Values and Python Equivalents

JSON defines a small set of value types. Each one maps cleanly to a Python type. Understanding this mapping is essential because json.loads() and json.load() automatically convert JSON values into their Python equivalents, and json.dumps() and json.dump() convert back.

JSON TypeJSON ExamplePython EquivalentNotes
String"hello"strJSON strings always use double quotes — single quotes are not valid JSON.
Number42 or 3.14int or floatJSON has one number type; Python distinguishes int and float by the presence of a decimal point.
Booleantrue / falseTrue / FalseJSON booleans are lowercase; Python's are capitalized. The module handles this automatically.
NullnullNoneBoth mean "absence of a value." Converted automatically in both directions.
Array[1, 2, 3]listOrdered, can hold mixed types, uses square brackets — just like Python lists.
Object{"key": "val"}dictKey-value pairs. JSON requires all keys to be strings. Python dictionaries allow other key types, but only string keys survive the JSON round-trip.
Watch Out

Two differences trip up beginners constantly. First, JSON requires double quotes around strings — including dictionary keys. Writing {'key': 'value'} looks like valid Python but is not valid JSON. Second, JSON booleans are lowercase: true and false, not True and False. The json module handles the conversion automatically, but when you write JSON by hand (in a file or a test string), you must use the JSON spelling.

Good to Know

When you load JSON into Python, you get back perfectly ordinary Python objects. A JSON array becomes a Python list you can index, slice, and iterate. A JSON object becomes a Python dictionary you can look up with square brackets or .get(). No special JSON-specific methods are needed after loading.

Importing the json Module

Python's standard library includes the json module. You do not need to install anything. You just import it at the top of your program.

import json

The json module provides four main functions. Learning to distinguish them is the most important step in this chapter.

FunctionInputOutputWhat It Does
json.loads(text)A stringA Python objectParses a JSON-formatted string into Python data.
json.dumps(obj)A Python objectA stringConverts a Python object into a JSON-formatted string.
json.load(file)An open file objectA Python objectReads JSON from a file and returns Python data.
json.dump(obj, file)A Python object + fileNothing (writes to file)Writes a Python object to a file in JSON format.
The Naming Pattern

The versions ending in s — json.loads() and json.dumps() — work with strings. The versions without the s — json.load() and json.dump() — work with file objects. The s stands for "string." Once you see this pattern, you will never mix them up.

Sections 31.4 – 31.7
The Four Functions: Converting and File I/O
json.loads · json.dumps · json.load · json.dump

Converting JSON Text to Python: json.loads()

The function json.loads() takes a string containing JSON text and returns the corresponding Python object. The name is read as "JSON load string."

import json

text = '{"name": "Theo", "score": 95, "passed": true}'

result = json.loads(text)

print(result)
print(type(result))
print(result["name"])
print(result["score"])
print(result["passed"])
Output {'name': 'Theo', 'score': 95, 'passed': True} <class 'dict'> Theo 95 True

Notice what happened: the JSON string became a Python dictionary, JSON's true became Python's True, and you can use the result exactly like any other dictionary.

Parsing a JSON Array

import json

text = '[10, 20, 30, 40, 50]'

result = json.loads(text)

print(result)
print(type(result))
print(result[2])
Output [10, 20, 30, 40, 50] <class 'list'> 30

Parsing Nested JSON

import json

text = '''
{
    "title": "Python Basics",
    "chapters": [1, 2, 3, 4],
    "author": {
        "name": "M. Gale",
        "email": "[email protected]"
    }
}
'''

data = json.loads(text)

print(data["title"])
print(data["chapters"])
print(data["author"]["name"])
Output Python Basics [1, 2, 3, 4] M. Gale
Tip

Triple-quoted strings let you write multi-line JSON text directly in your code, which is useful for testing before you connect a real file. When you are ready to move to actual files, you will switch to json.load() — the logic for accessing the returned data stays the same.

Converting Python to JSON Text: json.dumps()

The function json.dumps() takes a Python object and returns a JSON-formatted string. The name is read as "JSON dump string." This is the reverse of json.loads().

import json

student = {
    "name": "Priya Nair",
    "gpa": 3.85,
    "enrolled": True,
    "courses": ["CS101", "MATH150"]
}

text = json.dumps(student)
print(text)
print(type(text))
Output {"name": "Priya Nair", "gpa": 3.85, "enrolled": true, "courses": ["CS101", "MATH150"]} <class 'str'>

Notice that Python's True became JSON's true. The output is compact — everything on one line. For human-readable output, use the indent parameter.

text = json.dumps(student, indent=2)
print(text)
Output { "name": "Priya Nair", "gpa": 3.85, "enrolled": true, "courses": [ "CS101", "MATH150" ] }

Useful Keyword Arguments

ArgumentExampleEffect
indent=2json.dumps(data, indent=2)Adds line breaks and two-space indentation for readability.
indent=4json.dumps(data, indent=4)Same as above with four-space indentation.
sort_keys=Truejson.dumps(data, sort_keys=True)Sorts dictionary keys alphabetically, making output predictable.
text = json.dumps(student, indent=4, sort_keys=True)
print(text)
Output { "courses": [ "CS101", "MATH150" ], "enrolled": true, "gpa": 3.85, "name": "Priya Nair" }
When to Use indent

Use indent=2 or indent=4 when writing files that humans might open and read — configuration files, saved records, debug output. Skip the indent when writing files that only programs will read. Compact JSON is slightly smaller and faster to parse.

Reading JSON from a File: json.load()

Most real JSON data lives in files, not strings typed by hand. The function json.load() reads JSON directly from an open file object. It is the file-based counterpart of json.loads().

Suppose you have a file called config.json with this content:

{
  "app_name": "StudyTracker",
  "version": "1.0",
  "debug": false,
  "max_students": 100
}

You read it like this:

import json

with open("config.json", "r") as f:
    config = json.load(f)

print(config["app_name"])
print(config["version"])
print(config["debug"])
print(config["max_students"])
Output StudyTracker 1.0 False 100
The Pattern

1. Open the file with open() using "r" mode (read).
2. Call json.load(f) to parse the entire file contents into a Python object.
3. The with block closes the file automatically.
4. Use the result like any Python dictionary or list — no special JSON methods needed.

Reading a JSON Array from a File

Suppose students.json contains a JSON array at the top level:

[
  {"name": "Ada", "score": 88},
  {"name": "Ren", "score": 74},
  {"name": "Paz", "score": 95}
]
import json

with open("students.json", "r") as f:
    students = json.load(f)

for student in students:
    print(student["name"], student["score"])
Output Ada 88 Ren 74 Paz 95

JSON arrays at the top level become Python lists. You iterate over them exactly as you would any list of dictionaries — skills you already have from Chapter 19.

Writing JSON to a File: json.dump()

The function json.dump() writes a Python object to an open file as JSON text. It is the file-based counterpart of json.dumps().

import json

inventory = [
    {"item": "pencil",   "quantity": 120, "price": 0.25},
    {"item": "notebook", "quantity": 45,  "price": 2.99},
    {"item": "ruler",    "quantity": 30,  "price": 1.50}
]

with open("inventory.json", "w") as f:
    json.dump(inventory, f, indent=2)

print("Inventory saved.")
Output Inventory saved.

After running this program, inventory.json will contain:

[
  {
    "item": "pencil",
    "quantity": 120,
    "price": 0.25
  },
  {
    "item": "notebook",
    "quantity": 45,
    "price": 2.99
  },
  {
    "item": "ruler",
    "quantity": 30,
    "price": 1.5
  }
]
The Pattern

1. Open the file with open() using "w" mode (write). This creates the file if it does not exist and overwrites it if it does.
2. Call json.dump(data, f, indent=2) to write the data.
3. The with block closes the file.

Important: "w" mode always overwrites the entire file. This is the right behavior when saving program state — you want the file to contain the current complete state, not old data plus new data appended together.

Do Not Use Append Mode

Do not open a JSON file in "a" (append) mode to add records. A JSON file is one complete structure from start to finish. Appending to it would break the JSON format and make the file unparseable. Always overwrite the whole file with the updated data.

Sections 31.8 – 31.10
Patterns, Nested Data, and Errors
Read-Modify-Write · Nested Structures · Error Handling

A Complete Read-Modify-Write Pattern

Many programs follow a cycle: read data from a file, modify it in memory, write it back. JSON files fit this pattern well. Here is a complete example — a program that manages a simple task list stored in tasks.json.

import json
import os

FILENAME = "tasks.json"


def load_tasks():
    if os.path.exists(FILENAME):
        with open(FILENAME, "r") as f:
            return json.load(f)
    return []


def save_tasks(tasks):
    with open(FILENAME, "w") as f:
        json.dump(tasks, f, indent=2)


def add_task(tasks, description):
    task = {
        "description": description,
        "done": False
    }
    tasks.append(task)


def mark_done(tasks, index):
    if 0 <= index < len(tasks):
        tasks[index]["done"] = True
    else:
        print("Invalid task number.")


def show_tasks(tasks):
    if not tasks:
        print("No tasks.")
        return
    for i, task in enumerate(tasks):
        status = "DONE" if task["done"] else "TODO"
        print(f"  [{i}] {status}  {task['description']}")


tasks = load_tasks()

add_task(tasks, "Read Chapter 31")
add_task(tasks, "Complete the exercises")
add_task(tasks, "Review key terms")

mark_done(tasks, 0)

show_tasks(tasks)

save_tasks(tasks)
print("Tasks saved.")
Output [0] DONE Read Chapter 31 [1] TODO Complete the exercises [2] TODO Review key terms Tasks saved.

Design Walk-Through

FunctionDesign decision and why
load_tasks()Checks whether the file exists before trying to read it. If not, returns an empty list. This prevents a crash on first run — a critical safety measure.
save_tasks()Always overwrites the file with the complete current state. No partial writes, no appending.
add_task()Each task is a dictionary with a "description" and a "done" boolean. This is the JSON-friendly record shape.
mark_done()Validates the index before modifying, preventing an IndexError.
Main codeCreate tasks, modify one, display all, save. The separation into small functions makes the program easy to test and extend.
This Is a Realistic Program

This pattern — load from JSON, work in memory, save back to JSON — is used in real applications: note-taking tools, local game saves, configuration managers, and small personal databases. Once you know it, you can build a large family of useful programs without needing a database system.

JSON and Nested Data

JSON handles nested structures naturally. You already know how to work with nested Python dictionaries and lists from Chapter 19. Those skills transfer directly — the only new thing is that the data may arrive from a JSON file rather than being written by hand in your source code.

import json

student = {
    "name": "Leila Okonkwo",
    "id": "MGU-3017",
    "gpa": 3.6,
    "courses": [
        {"code": "CS201",    "grade": "A"},
        {"code": "MATH201",  "grade": "B+"},
        {"code": "PHYS101",  "grade": "A-"}
    ],
    "contact": {
        "email": "[email protected]",
        "phone": "555-0192"
    }
}

# Access nested data
print(student["name"])
print(student["contact"]["email"])
print(student["courses"][0]["code"])

for course in student["courses"]:
    print(f"  {course['code']}: {course['grade']}")
Output Leila Okonkwo [email protected] CS201 CS201: A MATH201: B+ PHYS101: A-

The Round-Trip: Save Then Load

Saving and loading nested data works exactly the same as simpler data:

with open("student.json", "w") as f:
    json.dump(student, f, indent=2)

with open("student.json", "r") as f:
    loaded = json.load(f)

print(loaded["name"])
print(loaded["courses"][1]["grade"])
Output Leila Okonkwo B+
Round-Trip Reliability

The round-trip — json.dump() then json.load() — preserves every value, every type, and every level of nesting. A list stays a list. A dictionary stays a dictionary. A boolean stays a boolean (though it travels as true/false inside the file and returns as True/False in Python). None travels as null and returns as None. You do not need to do any type conversion manually.

Handling JSON Errors

Two main things can go wrong with JSON. A robust program handles both.

Invalid JSON Text: JSONDecodeError

If a file or string contains malformed JSON — a missing quote, a trailing comma, a single-quoted key — json.loads() or json.load() raises a json.JSONDecodeError.

import json

bad_text = "{'name': 'Ali'}"   # Single quotes — not valid JSON

try:
    data = json.loads(bad_text)
except json.JSONDecodeError as e:
    print("JSON error:", e)
Output JSON error: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)

The error message tells you exactly where the problem is. Single-quoted keys are the most common hand-written JSON mistake.

Missing File: FileNotFoundError

If you call json.load() on a file that does not exist, you get a FileNotFoundError — the same error you learned in Chapter 21.

import json

try:
    with open("missing.json", "r") as f:
        data = json.load(f)
except FileNotFoundError:
    print("File not found. Starting with empty data.")
    data = {}
Output File not found. Starting with empty data.

A Reusable Safe-Loading Function

A robust program handles both errors. Here is a safe loading function you can reuse across projects:

import json
import os


def load_json_file(filename, default=None):
    """
    Load JSON from a file. Return default if the file does not exist
    or contains invalid JSON.
    """
    if default is None:
        default = {}

    if not os.path.exists(filename):
        return default

    try:
        with open(filename, "r") as f:
            return json.load(f)
    except json.JSONDecodeError as e:
        print(f"Warning: Could not read {filename}: {e}")
        return default

Use it like this:

config   = load_json_file("config.json",   default={"debug": False})
students = load_json_file("students.json", default=[])

If either file is missing or broken, the program continues with safe default values instead of crashing. Providing a sensible default is always better than letting an uncaught exception stop the program.

Sections 31.11 – 31.12
Choosing Formats and Practical Patterns
JSON vs CSV · Configuration · State · Record Collections

JSON Versus CSV: Choosing the Right Format

You now know three formats for storing data in files: plain text, CSV, and JSON. The right choice depends on the shape of your data, not personal preference.

FeaturePlain TextCSVJSON
Flat table dataPoorExcellentGood
Nested recordsPoorPoorExcellent
Human readableGoodGoodGood
Lists inside recordsNoNoYes
Mixed field typesNoManualYes
Open in a spreadsheetNoYesNo
Standard formatNoYesYes
Boolean valuesNoNoYes
Null / missing valuesNoWorkaroundYes
The Rule of Thumb

Use plain text for unstructured prose — logs, notes, reports, long written content.

Use CSV when your data is a flat table — every record has the same fields, all values are simple strings or numbers, and you might open the file in a spreadsheet.

Use JSON when your records have layers — a list of grades inside a student, an address dictionary inside a contact, optional fields, or boolean flags. JSON is also the right choice when you need to save program state and reload it later.

Practical Patterns for JSON Programs

Most JSON programs in Python fall into one of three patterns. Recognizing the pattern makes the program easier to plan and write.

Pattern 1: Configuration File

Many programs read a JSON configuration file at startup rather than hardcoding settings in the source code. This lets you change settings without editing Python.

import json


def load_config(filename="config.json"):
    with open(filename, "r") as f:
        return json.load(f)


config       = load_config()
app_name     = config.get("app_name",    "MyApp")
debug        = config.get("debug",       False)
max_items    = config.get("max_items",   50)
Use .get() with Defaults

Using .get(key, default) instead of [key] means the program keeps working even if the configuration file is missing some keys. This is especially important for optional settings that might not have been written yet.

Pattern 2: Saving Program State Between Sessions

import json
import os

STATE_FILE = "state.json"


def load_state():
    if os.path.exists(STATE_FILE):
        with open(STATE_FILE, "r") as f:
            return json.load(f)
    return {"high_score": 0, "games_played": 0}


def save_state(state):
    with open(STATE_FILE, "w") as f:
        json.dump(state, f, indent=2)


state = load_state()
state["games_played"] += 1
state["high_score"] = max(state["high_score"], 88)
save_state(state)

print("Games played:", state["games_played"])
print("High score:",   state["high_score"])

Each time the program runs, it reads the saved state, updates it, and writes it back. The state persists across sessions without any database or external tool.

Pattern 3: Building a Record Collection

import json


def add_record(records, new_record):
    records.append(new_record)


def find_by_field(records, field, value):
    return [r for r in records if r.get(field) == value]


def save_records(records, filename):
    with open(filename, "w") as f:
        json.dump(records, f, indent=2)


def load_records(filename):
    try:
        with open(filename, "r") as f:
            return json.load(f)
    except (FileNotFoundError, json.JSONDecodeError):
        return []


contacts = load_records("contacts.json")

add_record(contacts, {"name": "Sam Rivera", "city": "Portland", "active": True})
add_record(contacts, {"name": "Yuki Tanaka", "city": "Portland", "active": True})
add_record(contacts, {"name": "Ben Osei",    "city": "Salem",    "active": False})

portland = find_by_field(contacts, "city", "Portland")
for contact in portland:
    print(contact["name"])

save_records(contacts, "contacts.json")
Output Sam Rivera Yuki Tanaka
Catching Multiple Exceptions

Notice except (FileNotFoundError, json.JSONDecodeError) — you can catch multiple exception types in a single except clause by grouping them in a tuple. This keeps the safe-loading logic compact without needing two separate except blocks.

Chapter Summary

Key Terms

JSON
JavaScript Object Notation. A standard text format for representing structured data using strings, numbers, booleans, null, arrays, and objects. Both human-readable and machine-readable.
json module
Python's built-in module for reading and writing JSON data. Provides json.loads(), json.dumps(), json.load(), and json.dump().
json.loads()
Parses a JSON-formatted string and returns the corresponding Python object. The "s" stands for string.
json.dumps()
Converts a Python object into a JSON-formatted string. Accepts indent and sort_keys keyword arguments.
json.load()
Reads JSON data from an open file object and returns the corresponding Python object.
json.dump()
Writes a Python object to an open file object as JSON-formatted text.
JSONDecodeError
An exception raised by the json module when it encounters invalid JSON syntax — for example, single-quoted keys or trailing commas.
indent parameter
An optional argument to json.dumps() and json.dump() that adds line breaks and indentation to make the JSON output human-readable.
sort_keys parameter
An optional argument to json.dumps() and json.dump() that sorts dictionary keys alphabetically in the output, making the result predictable.
null
The JSON value representing absence of a value. Becomes Python None when loaded.
read-modify-write pattern
A common file-based data pattern in which a program reads a file into memory, changes the in-memory data, and writes the updated data back to the same file.
configuration file
A file that stores program settings separately from program code, allowing settings to change without editing the source.
round-trip
The process of writing data to JSON with json.dump() and reading it back with json.load(), with all types and structure preserved.
PracticeCode Reading

Read each code block and answer the questions without running the code. Only check with the interpreter after you have written your answers.

Exercise 31-R1

import json

data = json.loads('{"x": 10, "y": null, "active": false, "tags": ["a", "b"]}')

print(type(data))
print(data["y"])
print(data["active"])
print(len(data["tags"]))
print(data["tags"][1])
  1. What type does json.loads() return here?
  2. What does Python display for data["y"]?
  3. What does Python display for data["active"]?
  4. What is the length of data["tags"]?
  5. What does data["tags"][1] return?

Exercise 31-R2

import json
import os

def load_or_create(filename, default):
    if not os.path.exists(filename):
        with open(filename, "w") as f:
            json.dump(default, f, indent=2)
        return default
    with open(filename, "r") as f:
        return json.load(f)
  1. When does this function create a new file?
  2. What does it write to the new file?
  3. What does it return when the file already exists?
  4. What is the purpose of the default parameter?
  5. Could this function raise a JSONDecodeError? Under what conditions?

Exercise 31-R3

Trace through this code and write the exact output before checking.

import json

records = [
    {"name": "Finn", "score": 82},
    {"name": "Asha", "score": 91},
    {"name": "Eli",  "score": 74}
]

text   = json.dumps(records)
loaded = json.loads(text)

total = 0
for record in loaded:
    total += record["score"]

average = total / len(loaded)
print(f"Records: {len(loaded)}")
print(f"Average: {average:.1f}")
PracticeCode Tracing

Exercise 31-T1

Trace this code step by step. For each step, write what variable changes and what its new value is.

import json

state = {"level": 1, "score": 0, "lives": 3}

text = json.dumps(state)

state["score"] = 500
state["lives"] = 2

loaded = json.loads(text)

print(state["score"])
print(loaded["score"])
print(state["lives"])
print(loaded["lives"])
  1. What are the four printed values, in order?
  2. After tracing, explain why state and loaded have different values for "score" and "lives".
  3. At what line does loaded "take a snapshot" of the data?

Exercise 31-T2

Trace the values in this nested access chain. Write the output of each print() call, then explain the last line.

import json

text = '''
{
  "school": "MGU",
  "departments": [
    {"name": "Computer Science", "students": 240},
    {"name": "Mathematics",      "students": 185},
    {"name": "Physics",          "students": 120}
  ]
}
'''

data = json.loads(text)

print(data["school"])
print(data["departments"][1]["name"])
print(data["departments"][2]["students"])

total = sum(d["students"] for d in data["departments"])
print(total)
PracticePredict the Output

Exercise 31-P1

import json

original = {"a": 1, "b": [2, 3], "c": True}
text = json.dumps(original)
print(text)

Exercise 31-P2

import json

text = '[true, false, null, 42, "hello"]'
data = json.loads(text)

for item in data:
    print(type(item).__name__, item)

Exercise 31-P3 — Will it raise an error?

import json

bad = "{'key': 'value'}"

try:
    data = json.loads(bad)
    print("Parsed:", data)
except json.JSONDecodeError:
    print("Invalid JSON.")

Exercise 31-P4

import json

data = {"name": "River", "scores": [70, 85, 92]}
text = json.dumps(data, indent=2, sort_keys=True)
print(text)
PracticeDebugging and Error Analysis

Exercise 31-D1

This code may or may not crash. Trace it carefully. Explain what it does, whether it raises an error, and why.

import json

data = {'city': 'Portland', 'population': 650000}
text = json.dumps(data)
loaded = json.loads(text)
print(loaded['city'])

Exercise 31-D2 — Logic Error

This code is supposed to update a score and save it. Find the logic error and write the corrected version.

import json

with open("scores.json", "r") as f:
    data = json.load(f)

data["high_score"] = 9999

# Save to a different variable name by mistake
updated = json.dumps(data, indent=2)

print("Saved.")

Exercise 31-D3 — Missing File

What exception does this code raise and why? Write a version that handles the error gracefully.

import json

with open("settings.json", "r") as f:
    config = json.load(f)

print(config["theme"])

Exercise 31-D4 — Two Bugs

Find and fix both bugs in this code.

import json

students = [
    {name: "Yara", grade: 88},
    {name: "Dani", grade: 75}
]

with open("students.json", "w") as f:
    json.dump(students, f indent=2)
PracticeShort Programming Tasks
  1. Task 31-1. Write a program that creates a Python dictionary representing a book with keys "title", "author", "year", and "available" (a boolean). Convert the dictionary to a JSON string using json.dumps() with indent=2 and print the result.
  2. Task 31-2. Write a function called save_to_json(data, filename) that saves any Python object to a JSON file with indent=2. Then write a function called load_from_json(filename) that reads it back. Test both functions with a small dictionary.
  3. Task 31-3. Given the JSON string below, write code to print only the names of students who have a score above 80.
    text = '''
    [
      {"name": "Omar",   "score": 92},
      {"name": "Freya",  "score": 78},
      {"name": "Luz",    "score": 85},
      {"name": "Tariq",  "score": 63},
      {"name": "Sofia",  "score": 97}
    ]
    '''
  4. Task 31-4. Write a function called update_field(filename, key, value) that loads a JSON object from a file, updates the given key with the given value, and saves the updated object back to the same file. Test it by creating a settings.json file and calling update_field("settings.json", "debug", True).
  5. Task 31-5. Write a program that reads a list of student dictionaries from a JSON file, calculates the average score, and prints the name of the student with the highest score. Assume each student dictionary has a "name" key and a "score" key.
  6. Task 31-6. Write a program that builds a contacts list interactively. Load existing contacts from contacts.json if the file exists. Ask the user to enter a name and email. Add the new contact and save the updated list. Print how many contacts are now stored.
Mini-ProjectJSON-Based Study Tracker
Project: Study Tracker
A command-line program that stores and retrieves study session data in a JSON file
  • Data shapeEach subject entry has a "subject" name, a "sessions" count, and a "total_minutes" value. The file stores a list of these entries.
  • Feature 1View all subjects and their total study time, formatted as hours and minutes (for example: 125 minutes → 2h 5m).
  • Feature 2Log a study session — enter the subject name and how many minutes you studied. If the subject already exists, update its totals. If not, create it.
  • Feature 3Show the subject with the most total minutes studied.
  • PersistenceLoad data from study_log.json at startup; save back to the same file after every change. Handle the case where the file does not yet exist.
  • FunctionsUse separate functions for each major operation: load_log(), save_log(), add_session(), show_summary(), show_leader().
  • ExtensionAdd a "last_studied" field that records the date of the most recent session. Import datetime and convert the date to a string before saving (dates are not a JSON type and must be stored as strings).

Common Misconceptions

Misconception 1
"JSON is a Python feature."
JSON is a language-independent standard format that predates Python's adoption of it. The same JSON file can be read by JavaScript, Java, Go, Ruby, and dozens of other languages. Python's json module is a library that implements the standard — the format itself belongs to no language.
Misconception 2
"I can use single quotes in JSON just like in Python."
You cannot. JSON strictly requires double quotes around all strings and all keys. Writing {'key': 'value'} is valid Python syntax but is not valid JSON. If you pass it to json.loads(), you will get a JSONDecodeError. When writing JSON by hand, always use double quotes.
Misconception 3
"json.dumps() saves data to a file."
json.dumps() returns a string — it does not touch any file. To save to a file, you use json.dump() (without the s), passing an open file object as the second argument. The naming is easy to confuse: remember that the s means "string," not "save."
Misconception 4
"I should append new records to the JSON file as I add them."
A JSON file is one complete structure — a single object or array from the first character to the last. Appending text to it would produce an invalid file that cannot be parsed. The correct approach is to load the entire structure, add your new record to the in-memory list or dictionary, then write the whole thing back with json.dump() in "w" mode.
ReviewMixed Review
  1. Review 31-1 (from Chapter 18). Explain when you would use a dictionary instead of a list in Python. Then explain how Python dictionaries relate to JSON objects.
  2. Review 31-2 (from Chapter 19). Write code to access the email of the second student in this structure:
    data = {
        "class": "CS201",
        "students": [
            {"name": "Kenji", "email": "[email protected]"},
            {"name": "Mira",  "email": "[email protected]"}
        ]
    }
  3. Review 31-3 (from Chapter 21). What does the with statement do when opening files? Why is it preferable to calling open() and close() separately?
  4. Review 31-4 (from Chapter 23). Write a try/except block that catches both FileNotFoundError and json.JSONDecodeError when loading a JSON file, and prints a specific message for each case.
  5. Review 31-5 (from Chapter 14). The code below works but is poorly organized. Identify two specific improvements you could make by breaking it into functions.
    import json
    
    with open("tasks.json", "r") as f:
        tasks = json.load(f)
    
    done    = [t for t in tasks if t["done"]]
    pending = [t for t in tasks if not t["done"]]
    
    print(f"Done: {len(done)}")
    print(f"Pending: {len(pending)}")
    
    for t in pending:
        print(" -", t["description"])
Chapter 31 Checklist
Confirm your understanding before moving on
Concepts
  • I can explain what JSON is and name the six value types it supports.
  • I can identify the Python equivalent for each JSON type, including the boolean and null translations.
  • I can explain the naming pattern that distinguishes the string functions from the file functions in the json module.
  • I can explain why a JSON file must always be overwritten completely rather than appended to.
  • I can explain the read-modify-write pattern and when to use it.
  • I can describe the difference between JSON and CSV and give a rule of thumb for choosing between them.
Functions and Usage
  • I can use json.loads() to parse a JSON string into a Python object.
  • I can use json.dumps() with indent and sort_keys to produce human-readable JSON.
  • I can use json.load() with the with statement to read a JSON file.
  • I can use json.dump() with the with statement to write a JSON file.
  • I can write a safe loading function that handles FileNotFoundError and JSONDecodeError.
  • I can access nested data from a loaded JSON structure using the skills from Chapter 19.
Patterns
  • I can implement a configuration file pattern using .get() with default values.
  • I can implement a state persistence pattern that survives between program runs.
  • I can implement a record collection with load, add, find, and save functions.
  • I can plan which format — plain text, CSV, or JSON — is right for a given data shape.
Preview — Appendix J
Standard Library Beginner Map

Appendix J provides a reference map of the Python standard library — the most useful modules a beginning-to-intermediate programmer should know: math, random, datetime, csv, pathlib, os, sys, json, and statistics. This chapter introduced json in depth. Appendix J shows you what each of the other modules offers and how to start reading its documentation so you can explore them independently.

MGU Library Connection
  • Programming with Python — Appendix I: File and CSV Reference
  • Programming with Python — Appendix J: Standard Library Beginner Map
  • Programming with Python — Appendix H: Data Structure Reference
  • Companion resource: JSON Quick Reference Card
  • Prerequisite chapter: Chapter 19 (Nested Data Structures)
  • Prerequisite chapter: Chapter 21 (Files, Paths, and Text Data)
  • Prerequisite chapter: Chapter 23 (Exceptions and Error Handling)
  • Next specialized course: Python for Data Science (introduces requests, APIs, and JSON from web services)