← Back to course

JSON: Saving Structured Data

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:

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:

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:

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:

Simple.

Useful.

Very structured.

Very good.

Mini Challenge

Create:

library_json.py

Build a small library program that:

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:

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:

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.