SECTION 1
Historical Context & Motivation
People have been keeping address books for centuries — handwritten ledgers full of names, addresses, and phone numbers. As computers became mainstream, the challenge shifted to figuring out how to represent that same contact data in a format machines could understand. Early computer systems stored contacts in flat text files with rigid column widths, which made editing and searching slow and error-prone.
The invention of JSON (JavaScript Object Notation) in the early 2000s changed everything. JSON gave developers a lightweight, human-readable way to structure data using simple key-value pairs. Today, virtually every app on your phone — from your contacts list to social media — uses JSON behind the scenes to store and exchange information.
1970s • Flat File Databases
1990s • XML Emerges
2001 • JSON Is Born
2010s • JSON Becomes Universal
Today • JSON Everywhere
The core question this lesson addresses is straightforward: how do you organize a collection of contacts in Python so you can save them to a file, look someone up quickly, and update their information? By building a JSON-based address book, you'll master the practical combination of lists, dictionaries, and file I/O that appears in real-world software every day.
SECTION 2
Core Principles & Definitions
Before we start coding, let's nail down the foundational ideas that make a JSON address book work. Each concept builds on Python data structures you've already seen — lists and dictionaries — and connects them to the JSON file format for persistent storage.
Dictionary as a Contact Record
"name", "phone", and "email" to their values. Each dict represents one person's contact information.List of Dicts as the Address Book
JSON for Persistence
json module converts between Python objects and JSON files with just a few lines of code.Lookup — Finding a Contact
"name") matches the value you're searching for. This is a linear search through the collection.Update — Modifying a Contact
KEY TAKEAWAY
SECTION 3
Visual Explanation — Data Structure Map
The diagram below shows how a Python list of dictionaries maps directly to JSON syntax. Notice how Python's [] brackets become JSON arrays and Python's {} braces become JSON objects. The structure is nearly identical, which is why Python's json module can convert between them so easily.
json.dump() (writing to disk) and json.load() (reading from disk). The right side shows the resulting contacts.json file. Notice the nearly identical syntax.The key insight from this diagram is that Python dicts and JSON objects share the same structure: curly braces wrapping key-value pairs. Likewise, Python lists and JSON arrays both use square brackets to hold ordered collections. This natural mapping is what makes Python's json module so convenient — you don't need to write any custom parsing code.
SECTION 4
How It Works — The Core Operations
Every address book application relies on four fundamental operations, sometimes called CRUD: Create, Read, Update, and Delete. In this lesson we'll focus on three of those — creating contacts, looking them up (reading), and updating their data. Let's walk through the code patterns for each operation.
Creating the Address Book & Adding Contacts
You start by importing the json module and defining an empty list. Each time you want to add a contact, you build a dictionary with the appropriate keys and append it to the list. Here is the pattern:
Code Pattern — Add a Contact
import json
address_book = []
def add_contact(book, name, phone, email):
contact = {
"name": name,
"phone": phone,
"email": email
}
book.append(contact)
add_contact(address_book, "Alice Smith", "555-0101", "alice@mail.com")
add_contact(address_book, "Bob Jones", "555-0202", "bob@mail.com")Looking Up a Contact
A lookup searches through the list for a dictionary whose "name" key matches the search term. We use a for loop that returns the matching dict, or None if no match is found. This is a linear search — it checks each contact one by one, which works well for small-to-medium address books.
Code Pattern — Lookup
def lookup_contact(book, name):
for contact in book:
if contact["name"].lower() == name.lower():
return contact
return None
result = lookup_contact(address_book, "alice smith")
if result:
print(result["phone"]) # Output: 555-0101
else:
print("Contact not found.")Updating a Contact
An update combines the lookup step with direct dictionary assignment. Once you find the right contact, you simply reassign the value for any key you want to change. Because Python dicts are mutable (changeable in place), the modification applies immediately to the list without needing to remove and re-add the entry.
Code Pattern — Update
def update_contact(book, name, field, new_value):
contact = lookup_contact(book, name)
if contact:
contact[field] = new_value
return True
return False
update_contact(address_book, "Alice Smith", "phone", "555-9999")Saving & Loading with JSON
Code Pattern — Save & Load
def save_book(book, filename="contacts.json"):
with open(filename, "w") as f:
json.dump(book, f, indent=2)
def load_book(filename="contacts.json"):
with open(filename, "r") as f:
return json.load(f)
save_book(address_book)
loaded = load_book()
print(loaded[0]["name"]) # Output: Alice SmithThe json.dump() function serializes (converts) your Python list into JSON text and writes it to a file. The indent=2 argument makes the output human-readable with nice formatting. The json.load() function does the reverse — it reads the file and deserializes (converts back) the JSON text into a Python list of dictionaries.
SECTION 5
Data Flow & Program Architecture
Now that you understand each individual operation, let's zoom out and look at how these pieces fit together into a complete program. The diagram below shows the typical flow of a menu-driven address book application. The user picks an action, the program performs it, and then loops back to the menu.
Notice the loop structure in the diagram. The program doesn't exit after a single operation — it returns to the menu so the user can perform multiple actions in one session. This event loop pattern is extremely common in real applications. Also notice that saving happens after every change, ensuring data is never lost even if the program crashes unexpectedly.
Complete Menu Loop Code
def main():
try:
book = load_book()
except FileNotFoundError:
book = []
while True:
print("\n--- Address Book ---")
print("1. Add contact")
print("2. Lookup contact")
print("3. Update contact")
print("4. Quit")
choice = input("Choose an option: ")
if choice == "1":
name = input("Name: ")
phone = input("Phone: ")
email = input("Email: ")
add_contact(book, name, phone, email)
save_book(book)
print("Contact added!")
elif choice == "2":
name = input("Search name: ")
result = lookup_contact(book, name)
if result:
print(f"Name: {result['name']}")
print(f"Phone: {result['phone']}")
print(f"Email: {result['email']}")
else:
print("Not found.")
elif choice == "3":
name = input("Name to update: ")
field = input("Field (phone/email): ")
value = input("New value: ")
if update_contact(book, name, field, value):
save_book(book)
print("Updated!")
else:
print("Contact not found.")
elif choice == "4":
print("Goodbye!")
break
main()SECTION 6
Worked Example — Full Session
Let's trace through a complete session where we create an address book, add two contacts, look one up, update a phone number, and save everything to a JSON file. Follow each step carefully to see how the data changes.
Building and Using an Address Book
- 1
Step 1 — Initialize the Address Book
We start by importing thejsonmodule and creating an empty list. At this point,address_bookis[]— no contacts yet.address_book = []→ empty list, length 0 - 2
Step 2 — Add Two Contacts
We calladd_contact(address_book, "Alice Smith", "555-0101", "alice@mail.com")and thenadd_contact(address_book, "Bob Jones", "555-0202", "bob@mail.com"). Each call creates a new dict and appends it to the list.address_book now has 2 entries: index 0 is Alice, index 1 is Bob. - 3
Step 3 — Look Up Bob
We calllookup_contact(address_book, "bob jones"). The function loops through each dict. On index 0 it checks"Alice Smith".lower() == "bob jones"→ False. On index 1 it checks"Bob Jones".lower() == "bob jones"→ True. It returns Bob's dict.Returns{"name": "Bob Jones", "phone": "555-0202", "email": "bob@mail.com"} - 4
Step 4 — Update Alice's Phone Number
We callupdate_contact(address_book, "Alice Smith", "phone", "555-9999"). Internally, this callslookup_contactto find Alice's dict, then setscontact["phone"] = "555-9999". Because dicts are mutable, this changes the data inside the list directly.Alice's phone is now 555-9999 instead of 555-0101. - 5
Step 5 — Save to JSON
We callsave_book(address_book). This openscontacts.jsonin write mode and callsjson.dump()withindent=2. The file now contains the updated address book in clean, formatted JSON.Filecontacts.jsonwritten to disk with 2 contacts, Alice's phone updated.
SECTION 7
Strengths, Limitations & Alternatives
A JSON-based address book is a great starting project, but it's important to understand where it shines and where it falls short. Comparing it to alternative approaches helps you appreciate what you've built and where you might go next.
| Criterion | JSON File (Our Approach) | CSV File | SQL Database |
|---|---|---|---|
| Human-readable | Yes — nicely formatted with indentation | Yes — comma-separated rows | No — binary format, needs special tools |
| Nested data | Yes — dicts can contain lists or other dicts | No — flat rows only | Requires multiple tables with joins |
| Search speed | Slow for large data (linear search) | Slow for large data (linear search) | Fast — indexed queries |
| Setup complexity | Minimal — just import json | Minimal — import csv | Moderate — requires SQL knowledge |
| Best for | Small projects, config files, APIs | Spreadsheet-like data | Large apps with thousands of records |
Comparison of data storage approaches for contact management
KEY TAKEAWAY
SECTION 8
Connection to Advanced Concepts
The JSON address book you've built uses concepts that scale directly into professional software development. Understanding how your mini-project connects to more advanced tools will help you see where this learning path leads.
| What You Learned | Advanced Version |
|---|---|
| List of dicts in memory | ORM models (e.g., SQLAlchemy) that map Python objects to database rows |
| Linear search with a for loop | Database indexing and SQL WHERE clauses for instant lookups |
json.dump() / json.load() | REST API serialization frameworks (e.g., Flask-RESTful, Django REST) |
| Menu-driven text interface | Web front-end with HTML forms, or a GUI with Tkinter or PyQt |
| try/except for missing file | Comprehensive error handling, logging, and input validation |
From beginner project to professional tools
When you're ready for the next level, consider adding a delete function (completing CRUD), implementing input validation to reject empty names or improperly formatted emails, or even building a simple web interface using Flask. Each of these extensions uses the same structural patterns — lists, dicts, and JSON — that you've just learned.
SECTION 9
Practice Problems
Question 1: CONCEPTUAL
Show answer
append(), and extend each contact with additional fields later.Question 2: BASIC CALCULATION
count_contacts(book) that takes the address book list and returns the number of contacts it contains. Then write a call to it with an address book that has 3 entries and show the expected output.Show answer
def count_contacts(book):
return len(book). If you call print(count_contacts(address_book)) on a list with 3 dicts, the output is 3. The built-in len() function counts the number of items in a list, and since each item is one contact dict, this gives us the contact count directly.Question 3: INTERMEDIATE
delete_contact(book, name) that removes a contact by name (case-insensitive). The function should return True if the contact was found and deleted, or False if no match was found. Hint: you can use a loop with an index.Show answer
enumerate() to track the index while looping: def delete_contact(book, name):
for i, contact in enumerate(book):
if contact["name"].lower() == name.lower():
book.pop(i)
return True
return False. The pop(i) method removes the element at index i from the list. We return immediately after deletion because continuing to loop over a modified list can cause bugs. If the loop completes without finding a match, we return False.Question 4: APPLIED
"address" field to each contact that includes street, city, state, and zip. Show how you would structure a single contact dict with this nested address, and explain what advantage JSON has over CSV for storing this kind of data.Show answer
{"name": "Dana Park", "phone": "555-0404", "email": "dana@school.edu", "address": {"street": "123 Oak St", "city": "Springfield", "state": "IL", "zip": "62704"}}. JSON handles this naturally because values in a JSON object can themselves be objects (nested dicts). A CSV file can only store flat rows — you'd have to flatten the address into separate columns like address_street, address_city, etc., which becomes messy if you later want to add more nested structures. JSON preserves the logical grouping of related data.Question 5: CRITICAL THINKING
Show answer
address_book = {"Alice Smith": {"phone": "555-0101", "email": "alice@mail.com"}}. Dictionary key lookup in Python is O(1) on average (constant time), compared to O(n) for a list scan. This makes lookups nearly instant regardless of size. The trade-off is that dictionary keys must be unique, so you can't store two people with the exact same name without modifying the key (e.g., appending a number). You also lose the guaranteed ordering you get with a list, though in Python 3.7+ dicts do maintain insertion order. This trade-off between speed and flexibility is a fundamental concept in computer science known as choosing the right data structure for your use case.SUMMARY
Summary
Summary
In this lesson, you built a complete JSON-based address book using Python. You learned that a dictionary represents a single contact with key-value pairs like name, phone, and email. A list of dictionaries serves as the full address book, holding all contacts in an ordered collection. The json module provides json.dump() and json.load() to save data to disk and load it back, giving your program persistence across sessions.
You implemented three core operations: adding contacts by appending dicts to the list, looking up contacts with a case-insensitive linear search, and updating contacts by finding the right dict and reassigning its values. You also structured the whole program around a menu-driven loop — the same pattern used in real-world applications. These skills form the foundation for more advanced projects involving databases, web APIs, and graphical interfaces.