JSON: Saving Structured Data

Welcome back.
In the previous lesson, you learned modules and imports.
Your programs learned how to split code into multiple files.
You wrote projects like:
main.py
tasks.py
safe_input.py
Very good.
Your code is now more organized.
Less giant file.
Less code lasagna.
Less “where did I put that function?”
Excellent progress.
But now we have another problem.
In previous lessons, we saved data in plain text files.
For example:
Anna,anna@example.com,123456
Marco,marco@example.com,987654
This works.
But it is not perfect.
What if the data becomes more complex?
What if one contact has more fields?
What if you want to save a list of dictionaries?
What if the user writes a comma in the name?
Chaos.
Text files are useful.
But for structured data, JSON is better.
JSON lets us save data like this:
contact = {
"name": "Anna",
"email": "anna@example.com",
"phone": "123456"
}
into a file.
Then we can load it back later.
This is very important.
JSON is used everywhere.
APIs.
Web apps.
Configuration files.
Databases.
Frontend and backend communication.
Tiny scripts that accidentally become real software.
Very useful.
Very practical.
Very Python-friendly.
What You Will Learn
In this lesson, you will learn:
- what JSON is;
- why JSON is useful;
- how JSON looks;
- how JSON is similar to Python dictionaries and lists;
- how to import the
jsonmodule; - how to save data with
json.dump(); - how to load data with
json.load(); - how to save a dictionary;
- how to save a list of dictionaries;
- how to format JSON nicely with
indent; - how to handle missing JSON files;
- how to build a contact book using JSON;
- common beginner mistakes with JSON.
By the end of this lesson, your programs will be able to save structured data properly.
Not just simple lines.
Real data.
Lists.
Dictionaries.
Nested information.
Very powerful.
Very useful.
Very “now this is starting to look like a real app”.
What Is JSON?
JSON means:
JavaScript Object Notation
Do not panic.
Even though the name says JavaScript, JSON is used by many languages.
Python uses JSON.
JavaScript uses JSON.
Java uses JSON.
APIs use JSON.
Web apps use JSON.
Everybody uses JSON.
JSON is a text format for storing and exchanging data.
It looks like this:
{
"name": "Anna",
"email": "anna@example.com",
"phone": "123456"
}
Looks familiar?
It is very similar to a Python dictionary.
Python dictionary:
contact = {
"name": "Anna",
"email": "anna@example.com",
"phone": "123456"
}
JSON:
{
"name": "Anna",
"email": "anna@example.com",
"phone": "123456"
}
Very similar.
Not exactly the same.
But close enough to feel friendly.
Like cousins.
One speaks Python.
One speaks web.
Why JSON Is Useful
JSON is useful because it can store structured data.
For example, one contact:
{
"name": "Anna",
"email": "anna@example.com",
"phone": "123456"
}
A list of contacts:
[
{
"name": "Anna",
"email": "anna@example.com",
"phone": "123456"
},
{
"name": "Marco",
"email": "marco@example.com",
"phone": "987654"
}
]
This is much better than plain text like:
Anna,anna@example.com,123456
Marco,marco@example.com,987654
Why?
Because JSON keeps structure.
It knows that each contact has:
name
email
phone
It can store lists.
It can store dictionaries.
It can store nested data.
Plain text is like writing everything on a napkin.
JSON is like using a small organized form.
Still text.
But with structure.
Very nice.
Importing the json Module
Python has a built-in module for JSON.
It is called:
json
To use it, import it:
import json
You do not need to install anything.
Python already includes it.
Very convenient.
Very civilized.
Very “finally, something works without installing seventeen packages”.
Basic idea:
import json
Then you can use:
json.dump()
json.load()
These two functions are very important.
dump saves data to a file.
load loads data from a file.
Simple.
Almost too simple.
Do not worry.
We will make it confusing later.
That is how programming works.
Saving a Dictionary to JSON
Create a file:
save_contact.py
Write:
import json
contact = {
"name": "Anna",
"email": "anna@example.com",
"phone": "123456"
}
with open("contact.json", "w") as file:
json.dump(contact, file)
Run:
python save_contact.py
or:
python3 save_contact.py
Python creates a file:
contact.json
Inside it, you may see:
{"name": "Anna", "email": "anna@example.com", "phone": "123456"}
This is valid JSON.
Not very pretty.
But valid.
This line saves the dictionary:
json.dump(contact, file)
Important:
json.dump(data, file)
means:
Save this Python data into this file as JSON.
Very useful.
Very clean.
Very structured.
Pretty JSON with indent
The previous JSON works, but it is all on one line.
We can make it prettier.
Use indent=4.
Example:
import json
contact = {
"name": "Anna",
"email": "anna@example.com",
"phone": "123456"
}
with open("contact.json", "w") as file:
json.dump(contact, file, indent=4)
Now contact.json looks like:
{
"name": "Anna",
"email": "anna@example.com",
"phone": "123456"
}
Much better.
Much easier to read.
Much less “JSON spaghetti”.
Use indent=4 when you want human-readable JSON.
Computers do not care.
Humans care.
And unfortunately, you are human.
Most days.
Loading a Dictionary from JSON
Now let us read the JSON file back.
Create a file:
load_contact.py
Write:
import json
with open("contact.json", "r") as file:
contact = json.load(file)
print(contact)
print(contact["name"])
print(contact["email"])
print(contact["phone"])
Output:
{'name': 'Anna', 'email': 'anna@example.com', 'phone': '123456'}
Anna
anna@example.com
123456
This line loads JSON from the file:
contact = json.load(file)
After loading, contact is a Python dictionary again.
That is the magic.
Not fake magic.
Useful magic.
Workflow:
Python dictionary
save as JSON
load from JSON
Python dictionary again
Very important.
Very practical.
Very real-world.
json.dump() vs json.load()
These two names are important.
json.dump(data, file)
saves data to a file.
json.load(file)
loads data from a file.
Remember:
dump = save
load = read
Example:
json.dump(contact, file)
means:
Save contact into file.
Example:
contact = json.load(file)
means:
Read JSON from file and convert it into Python data.
This pair is used constantly.
If you remember only one thing from this lesson, remember:
json.dump() saves.
json.load() loads.
That is the heart of JSON file handling.
Very small heart.
Very useful heart.
Saving a List to JSON
JSON can store lists too.
Create:
save_tasks_json.py
Write:
import json
tasks = [
"Buy milk",
"Study Python",
"Drink coffee"
]
with open("tasks.json", "w") as file:
json.dump(tasks, file, indent=4)
The file tasks.json will contain:
[
"Buy milk",
"Study Python",
"Drink coffee"
]
This is a JSON array.
In Python, we call it a list.
In JSON, it is called an array.
Same idea.
Different vocabulary.
Programming loves giving the same thing different names.
Keeps us humble.
And slightly annoyed.
Loading a List from JSON
Create:
load_tasks_json.py
Write:
import json
with open("tasks.json", "r") as file:
tasks = json.load(file)
for task in tasks:
print(f"- {task}")
Output:
- Buy milk
- Study Python
- Drink coffee
The JSON array became a Python list.
Very useful.
This is better than reading lines manually.
With plain text, we did:
for line in file:
tasks.append(line.strip())
With JSON, Python can load the whole structure automatically:
tasks = json.load(file)
Cleaner.
More powerful.
Less manual string surgery.
Excellent.
Saving a List of Dictionaries
This is where JSON becomes very useful.
Create:
save_contacts_json.py
Write:
import json
contacts = [
{
"name": "Anna",
"email": "anna@example.com",
"phone": "123456"
},
{
"name": "Marco",
"email": "marco@example.com",
"phone": "987654"
}
]
with open("contacts.json", "w") as file:
json.dump(contacts, file, indent=4)
The file contacts.json will contain:
[
{
"name": "Anna",
"email": "anna@example.com",
"phone": "123456"
},
{
"name": "Marco",
"email": "marco@example.com",
"phone": "987654"
}
]
This is much better than:
Anna,anna@example.com,123456
Marco,marco@example.com,987654
Because the structure is clear.
Each contact is an object.
Each object has keys.
The list contains contacts.
Beautiful.
Organized.
Like a filing cabinet.
But less dusty.
Loading a List of Dictionaries
Create:
load_contacts_json.py
Write:
import json
with open("contacts.json", "r") as file:
contacts = json.load(file)
for contact in contacts:
print("-----")
print(f"Name: {contact['name']}")
print(f"Email: {contact['email']}")
print(f"Phone: {contact['phone']}")
Output:
-----
Name: Anna
Email: anna@example.com
Phone: 123456
-----
Name: Marco
Email: marco@example.com
Phone: 987654
Now you can save and load structured contact data.
This is real.
This pattern appears everywhere.
A list of dictionaries is one of the most common beginner data structures.
JSON saves it beautifully.
Python loads it easily.
Very nice.
Very practical.
Very backend-friendly.
Python and JSON Types
Python data becomes JSON data.
Basic conversion:
Python dict -> JSON object
Python list -> JSON array
Python string -> JSON string
Python int/float -> JSON number
Python True -> JSON true
Python False -> JSON false
Python None -> JSON null
Example Python:
data = {
"name": "Anna",
"age": 25,
"active": True,
"skills": ["Python", "HTML", "CSS"],
"address": None
}
JSON:
{
"name": "Anna",
"age": 25,
"active": true,
"skills": [
"Python",
"HTML",
"CSS"
],
"address": null
}
Notice:
True becomes true
False becomes false
None becomes null
Small differences.
Important differences.
JSON is not exactly Python.
It just looks like Python after drinking tea with JavaScript.
Important JSON Rules
JSON has rules.
Very strict rules.
Strings must use double quotes:
"name"
Not single quotes:
'name'
Correct JSON:
{
"name": "Anna"
}
Invalid JSON:
{
'name': 'Anna'
}
Also, JSON does not allow trailing commas.
Correct:
{
"name": "Anna",
"age": 25
}
Wrong:
{
"name": "Anna",
"age": 25,
}
Python dictionaries allow more flexibility.
JSON is stricter.
Very strict.
Like a teacher with a ruler.
But for data.
Handling Missing JSON Files
If you try to load a JSON file that does not exist, Python gives:
FileNotFoundError
Example:
import json
with open("missing.json", "r") as file:
data = json.load(file)
Safer version:
import json
try:
with open("contacts.json", "r") as file:
contacts = json.load(file)
except FileNotFoundError:
contacts = []
print(contacts)
If the file exists, contacts are loaded.
If it does not exist, contacts becomes an empty list.
This is useful for first run.
New program.
No data yet.
No disaster.
Just an empty list waiting for life.
Very poetic.
For a file.
Saving After Changes
When working with JSON, the usual pattern is:
load data
change data
save data
Example:
import json
try:
with open("contacts.json", "r") as file:
contacts = json.load(file)
except FileNotFoundError:
contacts = []
new_contact = {
"name": "Sofia",
"email": "sofia@example.com",
"phone": "555000"
}
contacts.append(new_contact)
with open("contacts.json", "w") as file:
json.dump(contacts, file, indent=4)
This does:
Load contacts.
If file missing, start with empty list.
Add new contact.
Save updated list.
This pattern is extremely important.
You will use it again and again.
Load.
Modify.
Save.
The holy triangle of small data programs.
Very useful.
Very practical.
Very easy to forget one side and wonder why nothing saved.
Mini Program: Save Profile as JSON
Create:
profile_json.py
Write:
import json
profile = {
"name": input("Name: "),
"city": input("City: "),
"job": input("Job: ")
}
with open("profile.json", "w") as file:
json.dump(profile, file, indent=4)
print("Profile saved.")
Example input:
Name: Anna
City: Rome
Job: Developer
File profile.json:
{
"name": "Anna",
"city": "Rome",
"job": "Developer"
}
This is simple.
But powerful.
You saved a dictionary as structured data.
You can read it later.
You can edit it.
You can use it in another program.
Small project.
Real idea.
Mini Program: Load Profile from JSON
Create:
show_profile_json.py
Write:
import json
try:
with open("profile.json", "r") as file:
profile = json.load(file)
print("----- Profile -----")
print(f"Name: {profile['name']}")
print(f"City: {profile['city']}")
print(f"Job: {profile['job']}")
except FileNotFoundError:
print("No profile found.")
Output:
----- Profile -----
Name: Anna
City: Rome
Job: Developer
Now your program can load structured data.
This is a big step.
A plain text profile would require manual parsing.
JSON makes it direct.
No splitting.
No commas.
No string surgery.
Just load and use.
Beautiful.
Mini Program: JSON Task Manager
Create:
json_task_manager.py
Write:
import json
FILE_NAME = "tasks.json"
def load_tasks():
try:
with open(FILE_NAME, "r") as file:
return json.load(file)
except FileNotFoundError:
return []
def save_tasks(tasks):
with open(FILE_NAME, "w") as file:
json.dump(tasks, file, indent=4)
def show_menu():
print("----- JSON Task Manager -----")
print("1. Add task")
print("2. Show tasks")
print("3. Remove task")
print("q. Quit")
def add_task(tasks):
task = {
"title": input("Task title: "),
"done": False
}
tasks.append(task)
save_tasks(tasks)
print("Task saved.")
def show_tasks(tasks):
if len(tasks) == 0:
print("No tasks yet.")
else:
print("Tasks:")
for index, task in enumerate(tasks, start=1):
status = "done" if task["done"] else "not done"
print(f"{index}. {task['title']} - {status}")
def remove_task(tasks):
if len(tasks) == 0:
print("No tasks to remove.")
return
show_tasks(tasks)
try:
task_number = int(input("Task number to remove: "))
index = task_number - 1
if index < 0 or index >= len(tasks):
print("Invalid task number.")
return
removed_task = tasks.pop(index)
save_tasks(tasks)
print(f"Removed: {removed_task['title']}")
except ValueError:
print("Please enter a valid number.")
tasks = load_tasks()
while True:
show_menu()
choice = input("Choose an option: ").lower()
if choice == "1":
add_task(tasks)
elif choice == "2":
show_tasks(tasks)
elif choice == "3":
remove_task(tasks)
elif choice == "q":
print("Goodbye.")
break
else:
print("Unknown option.")
This task manager saves tasks as dictionaries.
Each task has:
title
done
Example JSON:
[
{
"title": "Study Python",
"done": false
},
{
"title": "Drink coffee",
"done": false
}
]
This is better than a plain list of task names.
Because now each task can have more information.
Later you can add:
priority
deadline
category
created_at
JSON grows better than plain text.
Plain text is good for simple data.
JSON is better for structured data.
Mini Program: JSON Contact Book
Create:
json_contact_book.py
Write:
import json
FILE_NAME = "contacts.json"
def load_contacts():
try:
with open(FILE_NAME, "r") as file:
return json.load(file)
except FileNotFoundError:
return []
def save_contacts(contacts):
with open(FILE_NAME, "w") as file:
json.dump(contacts, file, indent=4)
def show_menu():
print("----- JSON Contact Book -----")
print("1. Add contact")
print("2. Show contacts")
print("q. Quit")
def add_contact(contacts):
contact = {
"name": input("Name: "),
"email": input("Email: "),
"phone": input("Phone: ")
}
contacts.append(contact)
save_contacts(contacts)
print("Contact saved.")
def show_contacts(contacts):
if len(contacts) == 0:
print("No contacts yet.")
else:
print("Contacts:")
for contact in contacts:
print("-----")
print(f"Name: {contact['name']}")
print(f"Email: {contact['email']}")
print(f"Phone: {contact['phone']}")
contacts = load_contacts()
while True:
show_menu()
choice = input("Choose an option: ").lower()
if choice == "1":
add_contact(contacts)
elif choice == "2":
show_contacts(contacts)
elif choice == "q":
print("Goodbye.")
break
else:
print("Unknown option.")
This is a real beginner app.
It uses:
- dictionaries;
- lists;
- files;
- JSON;
- functions;
- loops;
- conditions;
- error handling.
Very strong.
Very useful.
Very real.
Your contact data is now structured and saved properly.
This is much better than comma-separated text.
Because if later you add:
address
birthday
notes
company
JSON can handle it nicely.
Plain text starts sweating.
Common Mistake: Forgetting import json
Wrong:
with open("data.json", "w") as file:
json.dump(data, file)
If you did not write:
import json
Python gives:
NameError
Correct:
import json
with open("data.json", "w") as file:
json.dump(data, file)
Always import the module before using it.
Python is powerful.
But it does not automatically guess your imports.
It is not a mind reader.
Thankfully.
Common Mistake: Using dump and load Backwards
Wrong:
data = json.dump(file)
Wrong:
json.load(data, file)
Correct:
json.dump(data, file)
Correct:
data = json.load(file)
Remember:
dump takes data and file
load takes file and returns data
Simple memory trick:
dump data into a file
load data from a file
If this feels confusing, that is normal.
The names are short.
The confusion is long.
Practice helps.
Common Mistake: Trying to Save Unsupported Data
JSON can save common data types:
dict
list
str
int
float
bool
None
But not everything.
For example, this may fail:
data = {
"numbers": {1, 2, 3}
}
Why?
Because {1, 2, 3} is a set.
JSON does not support Python sets directly.
If you need to save a set, convert it to a list:
data = {
"numbers": list({1, 2, 3})
}
For beginners, use:
dictionaries
lists
strings
numbers
booleans
None
These work well with JSON.
Keep it simple.
Simple survives.
Complicated asks for coffee.
Common Mistake: Invalid JSON File
If the JSON file is broken, json.load() can fail.
Example invalid JSON:
{
"name": "Anna",
}
The trailing comma makes it invalid.
Python may raise:
json.JSONDecodeError
Safer loading:
import json
try:
with open("data.json", "r") as file:
data = json.load(file)
except FileNotFoundError:
data = []
except json.JSONDecodeError:
print("JSON file is broken.")
data = []
This handles:
missing file
broken JSON file
Very useful.
Especially when humans edit JSON manually.
Humans and commas are a dangerous combination.
Very dangerous.
Common Mistake: Forgetting to Save After Append
Wrong:
contacts = load_contacts()
contact = {
"name": "Anna",
"email": "anna@example.com",
"phone": "123456"
}
contacts.append(contact)
print("Contact added.")
This adds the contact only in memory.
But it does not save to file.
When the program ends, the new contact disappears.
Correct:
contacts.append(contact)
save_contacts(contacts)
Remember the pattern:
load
modify
save
If you modify data but do not save it, the file does not change.
Python is not going to save it emotionally.
You must tell it.
Clearly.
With code.
Common Mistake: Opening JSON with Append Mode
This is usually wrong:
with open("contacts.json", "a") as file:
json.dump(contact, file)
Appending JSON objects directly can create invalid JSON.
Example broken file:
{"name": "Anna"}{"name": "Marco"}
That is not a valid JSON list.
For JSON files, usually do this:
load existing list
append item to list
save whole list again
Example:
contacts = load_contacts()
contacts.append(new_contact)
save_contacts(contacts)
This keeps the JSON file valid.
Append mode is useful for plain text logs.
For JSON structured data, be careful.
Very careful.
JSON likes structure.
Not random objects glued together.
Practice
Create:
practice_json.py
Write a program that:
- asks the user for a book title;
- asks the user for an author;
- asks the user for a year;
- saves the book as JSON in
book.json; - loads the book back;
- prints the book nicely.
Example solution:
import json
book = {
"title": input("Title: "),
"author": input("Author: "),
"year": input("Year: ")
}
with open("book.json", "w") as file:
json.dump(book, file, indent=4)
with open("book.json", "r") as file:
loaded_book = json.load(file)
print("----- Book -----")
print(f"Title: {loaded_book['title']}")
print(f"Author: {loaded_book['author']}")
print(f"Year: {loaded_book['year']}")
This practice teaches:
- creating a dictionary;
- saving dictionary as JSON;
- loading JSON;
- using loaded dictionary.
Simple.
Useful.
Very structured.
Very good.
Mini Challenge
Create:
library_json.py
Build a small library program that:
- saves books to
books.json; - loads books on start;
- lets the user add a book;
- lets the user show all books;
- each book has title, author, and year;
- handles missing file safely.
Example structure:
import json
FILE_NAME = "books.json"
def load_books():
try:
with open(FILE_NAME, "r") as file:
return json.load(file)
except FileNotFoundError:
return []
def save_books(books):
with open(FILE_NAME, "w") as file:
json.dump(books, file, indent=4)
def add_book(books):
book = {
"title": input("Title: "),
"author": input("Author: "),
"year": input("Year: ")
}
books.append(book)
save_books(books)
print("Book saved.")
def show_books(books):
if len(books) == 0:
print("No books yet.")
else:
print("Books:")
for book in books:
print("-----")
print(f"Title: {book['title']}")
print(f"Author: {book['author']}")
print(f"Year: {book['year']}")
def show_menu():
print("----- Library -----")
print("1. Add book")
print("2. Show books")
print("q. Quit")
books = load_books()
while True:
show_menu()
choice = input("Choose an option: ").lower()
if choice == "1":
add_book(books)
elif choice == "2":
show_books(books)
elif choice == "q":
print("Goodbye.")
break
else:
print("Unknown option.")
This is a real small application.
It uses JSON to store a list of dictionaries.
That is a very important pattern.
Very useful for future projects.
You can adapt this structure for:
contacts
tasks
products
students
courses
expenses
workouts
The idea is the same.
Load data.
Modify data.
Save data.
Congratulations.
You are building reusable patterns.
That is how developers grow.
Slowly.
Painfully.
With snacks.
Extra Challenge: Improve the Task Manager
Take the JSON task manager and add a new option:
4. Mark task as done
Each task already has:
"done": False
Your job:
- show tasks with numbers;
- ask which task to mark as done;
- change
"done"toTrue; - save the tasks again.
Hint:
tasks[index]["done"] = True
save_tasks(tasks)
This is a very good challenge.
It teaches updating structured data.
Not just adding.
Not just deleting.
Updating.
That is what real apps do all the time.
Create.
Read.
Update.
Delete.
Later you will hear this called CRUD.
Very serious word.
Very simple idea.
Very useful.
Beginner Checklist
When JSON code does not work, check:
Did I import json?
Am I using json.dump(data, file)?
Am I using data = json.load(file)?
Did I open the file in the correct mode?
Did I use indent=4 for readable JSON?
Does the file exist before reading?
Should I handle FileNotFoundError?
Is the JSON file valid?
Did I accidentally leave a trailing comma?
Am I trying to save an unsupported Python type?
Did I save after changing the data?
Am I appending to JSON incorrectly?
JSON bugs are usually about:
file missing
invalid JSON
wrong dump/load usage
forgotten save
wrong data structure
Fix calmly.
Read the error message.
Check the file.
Check the data.
Drink coffee if needed.
But do not throw the laptop.
The laptop is innocent.
Usually.
Summary
Today you learned:
- JSON is a common format for structured data;
- JSON looks similar to Python dictionaries and lists;
- Python uses the built-in
jsonmodule; json.dump()saves Python data to a JSON file;json.load()loads JSON data from a file;indent=4makes JSON readable;- dictionaries can be saved as JSON objects;
- lists can be saved as JSON arrays;
- lists of dictionaries are very useful with JSON;
True,False, andNonebecometrue,false, andnull;- JSON requires double quotes;
- JSON does not allow trailing commas;
- missing JSON files can be handled with
FileNotFoundError; - broken JSON can raise
json.JSONDecodeError; - the common pattern is load, modify, save;
- JSON is better than plain text for structured data.
This is a big step.
Your programs can now save real structured data.
Not just lines of text.
They can save contacts.
Tasks.
Books.
Profiles.
Products.
Anything that fits into dictionaries and lists.
This is the kind of data structure used in real applications.
Small beginner programs are now becoming serious.
Still small.
But serious.
Like a tiny engineer with a clipboard.
Next Lesson
In the next lesson, we will learn virtual environments and installing packages.
So far, we used only Python’s built-in tools.
But real Python projects often use external packages.
You will learn how to create a virtual environment:
python -m venv .venv
Activate it.
Install packages with pip.
And keep project dependencies organized.
This is very important before building bigger projects.
Because installing packages globally is how chaos begins.
And we already have enough chaos.
Very Python.
Very real-world.
Very necessary.