Skip to content
Search lessons, topics, tests…
Esc

    ↑ ↓ moveEnter openEsc close
    Lesson18 / 20

    Keep your tasks: save, load and handle a damaged file

    Will your tasks survive a restart—and what if the saved file is damaged? Add saving in small steps and keep a failed load visible before allowing edits.

    Predict what survives a restart

    In the previous version, tasks exist only in the running process. This version turns the list into JSON text and writes it to study-planner.json in the working directory. On the next run, it reads the file and rebuilds the list. JSON is a text format for structured data; System.Text.Json is included with .NET.

    Startup: a missing file starts an empty plan; valid loaded tasks enter the menu; damaged JSON, invalid tasks or another handled read failure stops before the menu. In the menu, a and d edit memory. Only s attempts saving; q does not save.

    TryLoad treats FileNotFoundException as a new empty plan. Successful reading must deserialize and validate a nonnull list with nonnull tasks and nonblank titles. Invalid data or another handled read failure returns false;startup reports the problem and exits before the menu. In the menu,add/complete edits affect memory;only s invokes TrySave. Save success reports Saved;handled failure reports that tasks remain in memory. q quits without automatically saving.

    You add a task, type q and restart, without typing s. What should you expect?

    A The new task is automatically saved
    B The new task is lost unless it was saved earlier
    C The file is necessarily deleted
    D The list becomes a Dictionary
    Show answer

    Correct answer: B

    The save command is explicit. Quitting does not call TrySave.

    Option A: There is no automatic save in this version.

    Option C: Quitting does not delete the file.

    Option D: Restarting does not change the collection type.

    Add persistence in three checkpoints

    The declaration const string path gives a fixed filename a name; const means the program cannot assign a different value to that name later. A question mark after List<StudyTask> or StudyTask permits a null reference, just as string? did in the input lessons.

    Serialize converts the current list into JSON text. Deserialize creates objects from JSON text. Validation checks whether the loaded data makes sense for this application. Valid JSON can still contain null or an empty title. The bool returned by TryLoad tells the caller whether loading was acceptable; the out parameter carries the list.

    Start with your working planner from lesson 17 in a separate console project with its own small practice file. Add the System.IO and System.Text.Json imports. The existing AddTask, ShowTasks and MarkDone methods stay the same.

    CheckpointRead or addWhat proves it works?
    1. Save explicitlyTrySave and the s menu branchAdd a task, type s, and inspect study-planner.json. Quitting alone must not save a new edit.
    2. Load before the menuTryLoad and the startup if statementRestart from the same folder and list the task. A missing file starts an empty plan.
    3. Stop after a bad loadTryLoad validation and catch branchesIn this practice folder, back up the file, put {broken in it, and restart. The menu must not open or overwrite that file. Restore your backup afterward.

    For checkpoint 1, add the path constant, TrySave and the s branch while keeping the existing empty-list declaration. For checkpoint 2, add TryLoad, then replace that declaration with the startup guard shown below. The expression out List<StudyTask> tasks declares the task-list variable and lets TryLoad supply its value. Keep only one declaration of tasks at this level. At checkpoint 3, trace the handled failure branches and run the damaged-file check.

    Complete Program.cs — use after tracing the changes

    This complete replacement contains the app and persistence methods. Do not paste it below the earlier version. Find TrySave, TryLoad and the startup guard using the table above before reading the unchanged menu methods.

    C#
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Text.Json;
    
    const string path = "study-planner.json";
    if (!TryLoad(path, out List<StudyTask> tasks))
    {
        Console.WriteLine("Cannot load study-planner.json. Check or rename the file.");
        return;
    }
    Console.WriteLine("Study Planner");
    while (true)
    {
        Console.WriteLine("a=add l=list d=done s=save q=quit");
        string? command = Console.ReadLine();
        if (command == null || command == "q")
        {
            Console.WriteLine("Goodbye. Use s before quitting to save.");
            break;
        }
        if (command == "a") AddTask(tasks);
        else if (command == "l") ShowTasks(tasks);
        else if (command == "d") MarkDone(tasks);
        else if (command == "s")
        {
            if (TrySave(path, tasks)) Console.WriteLine("Saved.");
            else Console.WriteLine("Save failed. Tasks are still in memory.");
        }
        else Console.WriteLine("Unknown command.");
    }
    
    static void AddTask(List<StudyTask> tasks)
    {
        Console.WriteLine("Task title:");
        string? title = Console.ReadLine();
        if (string.IsNullOrWhiteSpace(title))
        {
            Console.WriteLine("Title required.");
            return;
        }
        tasks.Add(new StudyTask { Title = title.Trim() });
        Console.WriteLine("Added.");
    }
    
    static void ShowTasks(List<StudyTask> tasks)
    {
        if (tasks.Count == 0)
        {
            Console.WriteLine("No tasks yet.");
            return;
        }
        for (int i = 0; i < tasks.Count; i++)
        {
            string status = "todo";
            if (tasks[i].IsDone) status = "done";
            Console.WriteLine($"{i + 1}. [{status}] {tasks[i].Title}");
        }
    }
    
    static void MarkDone(List<StudyTask> tasks)
    {
        Console.WriteLine("Task number:");
        string? text = Console.ReadLine();
        if (!int.TryParse(text, out int number) || number < 1 || number > tasks.Count)
        {
            Console.WriteLine("Invalid task number.");
            return;
        }
        tasks[number - 1].IsDone = true;
        Console.WriteLine("Completed.");
    }
    
    static bool TryLoad(string path, out List<StudyTask> tasks)
    {
        tasks = new List<StudyTask>();
        try
        {
            string json = File.ReadAllText(path);
            List<StudyTask>? loaded = JsonSerializer.Deserialize<List<StudyTask>>(json);
            if (loaded == null) return false;
            foreach (StudyTask? task in loaded)
            {
                if (task == null || string.IsNullOrWhiteSpace(task.Title)) return false;
            }
            tasks = loaded;
            return true;
        }
        catch (FileNotFoundException) { return true; }
        catch (JsonException) { return false; }
        catch (IOException) { return false; }
        catch (UnauthorizedAccessException) { return false; }
    }
    
    static bool TrySave(string path, List<StudyTask> tasks)
    {
        try
        {
            string json = JsonSerializer.Serialize(tasks);
            File.WriteAllText(path, json);
            return true;
        }
        catch (IOException) { return false; }
        catch (UnauthorizedAccessException) { return false; }
    }
    
    class StudyTask
    {
        public string Title { get; set; } = "";
        public bool IsDone { get; set; }
    }

    Check the experience in two runs

    First run: add Read C#, type s, then q. Look for the JSON file. Second run: type l; the task should reappear. Complete it, save and restart again; it should remain done. When using dotnet run, keep using the same working directory so the relative file path refers to the same place.

    Test a failure deliberately

    In this practice project only, make a backup copy of the small JSON file, replace its contents with {broken, and run. Startup should report the load problem and stop. The program should not replace the damaged file with an empty list. Restore the backup afterward. TryLoad treats a FileNotFoundException as a new plan. It catches that specific case before the broader IOException case, because other read failures must be reported instead of being mistaken for a missing file.

    Why check loaded titles even after Deserialize succeeds?

    A Every JSON value is a valid task
    B Successful parsing does not prove the application’s data rules
    C It makes file access impossible
    D It changes the JSON format
    Show answer

    Correct answer: B

    Parsing checks whether data can be read as the target structure. The application also requires a nonblank task title.

    Option A: JSON can represent null, blank strings and many unrelated structures.

    Option C: Validation does not disable file I/O.

    Option D: Checking a title does not change serialization rules.

    This small file format assumes one user and one running editor. It is an educational save mechanism; a larger app would also need backups, safer replacement writes and a policy for conflicting edits. Keep real important data elsewhere while practising.

    A save fails because the folder is not writable. What does this program report?

    A Saved.
    B Save failed; tasks are still in memory
    C It silently clears all tasks
    D It claims the original file is repaired
    Show answer

    Correct answer: B

    TrySave returns false for the handled file access failure, and the caller reports it without clearing the list.

    Option A: Saved is printed only after TrySave returns true.

    Option C: There is no Clear operation on failure.

    Option D: The program makes no repair claim.

    Your turn

    Change the path constant to my-study-plan.json and update the filename in the startup error message to match. Add two tasks, save and restart. Both tasks should return in their original order. Explain why changing the filename makes an earlier save appear absent. Then point to the exact statement that changes the in-memory list and the exact statement that persists it.

    References: Microsoft — serialization and deserialization.

    Quick reference

    • Serialize objects into JSON text.
    • Deserialize text, then validate the resulting data.
    • Relative paths depend on the working directory.
    • Save explicitly before quitting.
    • Report read and write failures honestly.
    Sign in to mark lessons done and keep your place in the course.Sign in

    TechInterviewNotes Editorial Team · Updated 27 Oct 2026 ·