✦ Python Intermediate & OOP Level 2 final project

L2 · Concept 03 · Final Project: Task Manager

Keep Tasks
After the Program Ends.

Save your Task Manager data as JSON so it can return the next time the program runs.

Learning goalSave Task data as JSON and reconstruct Task objects without duplicating the manager list.
01 Learn02 Work locally03 Check your understanding

Learning as a guest

Learning as a guest? Save your progress!

Create a free account to learn with Sloby AI, check your understanding, and track your progress.

The entire platform is free throughout 2026. Foundations stays free forever.

Memory disappears; files persist.

A TaskManager list exists only while the program runs. JSON is familiar structured text: json.dump() writes Python lists and dictionaries to a file, while json.load() reads them back.

import json

def save_tasks(self, path="data/tasks.json"):
    data = {"task_id": task.task_id, "title": task.title, "completed": task.completed}
    with open(path, "w", encoding="utf-8") as file:
        json.dump([data], file, ensure_ascii=False, indent=2)

JSON cannot directly store a custom Task object. Save a dictionary with task_id, title, and completed, then create a new Task from that dictionary after loading.

save_tasks(path="data/tasks.json") and load_tasks(path="data/tasks.json") use the default path for normal work. In a test, pass a separate path so test data never changes your real data/tasks.json file. A successful load replaces self.tasks rather than appending to it, so repeated loading does not create duplicates.

↳

Clear file policy: If the selected path does not exist, load_tasks() leaves the current list unchanged. If JSON is invalid, report a readable error and keep the current list unchanged.

Reconstructing one Task
saved = {"task_id": "task-1", "title": "Read", "completed": True}
task = Task(saved["task_id"], saved["title"])
task.completed = saved["completed"]

The dictionary holds plain JSON values. The reconstructed Task is again an object with attributes and methods.

Save and load predictably

  1. Save plain dataConvert every Task into a dictionary, then use json.dump() with encoding="utf-8".
  2. Load a fresh listRead JSON with json.load(), reconstruct Task objects, and replace self.tasks only after successful loading.

Common JSON persistence mistakes

  • Trying to dump a Task object directlyjson.dump() needs dictionaries, lists, strings, numbers, booleans, and null-like values, not custom objects.

Keep Your Tasks Between Runs

Do this in VS Code

Continue in the Task Manager project from Concepts 28 and 29. You will add JSON persistence to this same project, not create or download a new one.

Your existing Task Manager project
task_manager/
├── main.py
├── models/
│   └── task.py
├── services/
│   └── task_manager.py
└── data/
    └── tasks.json

Keep the Task and TaskManager methods you already built. The data folder will hold a JSON file created by your program.

Add persistence step by step

  1. Open the existing projectOpen the same project folder in VS Code. You will work mainly in models/task.py, services/task_manager.py, main.py, and the data folder.
  2. Recognize the JSON shapeOne Task object becomes one dictionary. A JSON file stores a list of those dictionaries, not Python Task objects.
  3. Plan the object conversionWrite down the three values every saved task needs: task_id, title, and completed. Then decide how you will create a new Task and restore its completed value after reading a dictionary.
    task_data = {
        "task_id": task.task_id,
        "title": task.title,
        "completed": task.completed,
    }
  4. Implement save_tasks()In services/task_manager.py, import json and add an optional path parameter with the default "data/tasks.json". Convert the current list into plain dictionaries, then write that list to path with UTF-8 encoding. Calling save_tasks() uses the normal project file; a test can pass a separate path.
    def save_tasks(self, path="data/tasks.json"):
        with open(path, "w", encoding="utf-8") as file:
            json.dump(tasks_data, file, ensure_ascii=False, indent=2)
  5. Implement load_tasks() safelyGive load_tasks() the same optional path parameter. Read that path, reconstruct Task objects into a fresh list, and assign self.tasks only after all reading and reconstruction succeed. If the selected file is missing, leave the current list unchanged. If JSON is invalid, show a readable error and keep the current list.
    def load_tasks(self, path="data/tasks.json"):
        with open(path, encoding="utf-8") as file:
            saved_tasks = json.load(file)
  6. Demonstrate a complete save and loadIn main.py, add two tasks, save them, inspect the created JSON file, then start the program again and load the saved tasks before listing them.
    python3 main.py
    # or: python main.py
Example JSON file
[
  {"task_id": "task-1", "title": "Read the plan", "completed": false},
  {"task_id": "task-2", "title": "Write notes", "completed": true}
]

JSON uses plain values. The dictionary describes task data; your Python code reconstructs the Task object after loading.

↳

Manual check: add two tasks → save_tasks() → inspect data/tasks.json → restart → load_tasks() → list_tasks() After restarting, the two saved tasks should appear once. Test a missing file and invalid JSON separately without losing tasks already held in memory.

Before asking Sloby for help

  • Check the JSON file pathRun main.py from the project folder so the relative path data/tasks.json points to the data folder you expect.
  • Do not dump Task objects directlyA TypeError means json.dump() received a Task object. Convert every Task to a dictionary first.
  • Handle a missing file calmlyFileNotFoundError is a normal first-run case. Explain it clearly and leave the current task list unchanged.
  • Keep invalid JSON safeCatch JSONDecodeError, report that the file cannot be read, and do not replace self.tasks with partial data.
  • Avoid duplicate tasks after loadingDo not append loaded tasks to self.tasks. Build a fresh list and replace self.tasks only after successful loading.

What should load_tasks() do after successfully reading saved JSON?

Choose the behavior that prevents duplicate tasks after repeated loading.

manager.tasks = loaded_tasks
Choose the output

Build confidence independently

Prove that loading replaces the list

Test persistence rather than only copying the example. Use a task already in memory to show the difference between replacing and appending.

Your goalSave two tasks. Before loading, add a different temporary Task only to memory. Then load the saved file and show that the temporary Task is gone while the two saved tasks appear exactly once.
task_manager/
├── main.py
├── services/task_manager.py
└── data/tasks.json

Work in your local VS Code folder

Progressive hints
  • Use different task_id values for the two saved tasks and the temporary Task.
  • Make a new list for reconstructed Task objects instead of changing self.tasks during the loop.
  • Assign the new list to self.tasks only after json.load() and every reconstruction step have succeeded.

Ask Sloby for one guiding question about converting objects, file paths, or safe loading. Do not ask it to write the finished persistence methods for you.

Sloby, the Python AI Tutor mascot

Your learning mentor

Sign in to ask Sloby

Create a free account or sign in to get AI guidance while you learn.

Sign in to check your understanding

Create a free account or sign in to receive AI feedback and save your learning progress.

Python AI Tutor · Learn by understanding, not just by running code.