Day 78 のゴールと全体像
Day 78 では、タスク管理アプリに 「記憶力」を持たせていきます。 これまでは、アプリを終了すると、せっかく登録したタスクがすべて消えてしまう状態でした。 今日は、JSONファイルにタスクを保存し、次回起動時に読み込むことで、
アプリを閉じてもタスクが残り続ける
という、実用的な振る舞いを手に入れていきます。
「データを保存する」というのは、 アプリ開発の世界ではとても重要なテーマです。 その第一歩として、JSON保存をやさしく体験していきましょう。
JSONってそもそも何者?
人間にも読みやすいデータの形
JSON(ジェイソン)は、
- テキストで書かれた
- 「キーと値」の組み合わせを
- シンプルなルールで表現する
ためのフォーマットです。
例えば、タスクを JSON で表すと、こんな感じになります。
{
"Id": 1,
"Title": "レポート作成",
"Description": "明日までに提出",
"Priority": 1,
"DueDate": "2026-09-10T00:00:00",
"IsCompleted": false,
"CreatedAt": "2026-09-06T17:30:00"
}
JSON見てみると、
IdTitleDescription
など、C# の TaskItem クラスのプロパティ名がそのまま出てきています。 この「クラスの形とほぼ同じ形で保存できる」というのが、JSONの大きな魅力です。
どこに保存するかを決める
ファイルパスを決める
コンソールアプリでは、 まず「どのファイルに保存するか」を決める必要があります。
今回は、アプリと同じフォルダに tasks.json というファイルを作ることにしましょう。
// JSONファイルのパス(今回はカレントディレクトリに保存)
const string TaskFilePath = "tasks.json";
C#この定数を Program クラスの中に置いておくと、 アプリ全体で同じファイルを参照できて便利です。
JSON保存に使うライブラリを準備する
System.Text.Json を使う
C# では、標準ライブラリとして System.Text.Json が用意されています。 これを使うことで、
- オブジェクト → JSON文字列(シリアライズ)
- JSON文字列 → オブジェクト(デシリアライズ)
を簡単に行うことができます。
必要な using は次のとおりです。
using System.Text.Json;
using System.IO;
C#System.IO はファイルの読み書きに使います。
TaskRepository に「保存」と「読み込み」を追加する
タスク一覧を丸ごと保存する
まずは、TaskRepository に「今持っているタスク一覧を JSON に保存する」メソッドを追加します。
class TaskRepository
{
private readonly List<TaskItem> _tasks = new List<TaskItem>();
private int _nextId = 1;
// 既存のメソッドは省略...
// タスク一覧をJSONファイルに保存する
public void SaveToFile(string filePath)
{
// 1. シリアライズ用のオプションを設定(見やすいようにインデントを付ける)
var options = new JsonSerializerOptions
{
WriteIndented = true // JSONを整形して保存(人間にも読みやすく)
};
// 2. タスク一覧をJSON文字列に変換
string json = JsonSerializer.Serialize(_tasks, options);
// 3. ファイルに書き込む
File.WriteAllText(filePath, json);
}
// JSONファイルからタスク一覧を読み込む
public void LoadFromFile(string filePath)
{
// ファイルが存在しない場合は、何もせずに戻る
if (!File.Exists(filePath))
{
return;
}
// 1. ファイルからJSON文字列を読み込む
string json = File.ReadAllText(filePath);
// 2. JSON文字列をList<TaskItem>に変換
var tasks = JsonSerializer.Deserialize<List<TaskItem>>(json);
if (tasks == null)
{
// 変換に失敗した場合は何もしない(簡易的なエラーハンドリング)
return;
}
// 3. 内部のリストを読み込んだデータで置き換える
_tasks.Clear();
_tasks.AddRange(tasks);
// 4. 次に割り当てるIDを調整する
if (_tasks.Any())
{
_nextId = _tasks.Max(t => t.Id) + 1;
}
else
{
_nextId = 1;
}
}
}
C#重要ポイントをじっくり解説
WriteIndented = trueJSONを「インデント付き」で保存することで、 ファイルを開いたときに人間にも読みやすくなります。 デバッグや学習のときには、とてもありがたい設定です。SerializeとDeserializeSerialize:オブジェクト → JSON文字列Deserialize:JSON文字列 → オブジェクト という役割を持っています。List<TaskItem>をそのまま渡せるのが嬉しいところです。
- ファイルがない場合の扱い 初回起動時など、まだ
tasks.jsonが存在しないこともあります。 その場合は、何もせずに戻ることで、 「空の状態からスタートする」挙動になります。 _nextIdの調整 読み込んだタスクの最大IDを調べて、 その次の番号を_nextIdに設定しています。 これをしないと、再起動後にIDが1から再スタートしてしまい、 既存タスクとIDがかぶってしまうので注意が必要です。
アプリ起動時にJSONを読み込む
Mainの最初で読み込む
アプリを起動したときに、 前回保存したタスクを読み込むようにします。
class Program
{
static TaskRepository repository = new TaskRepository();
const string TaskFilePath = "tasks.json";
static void Main()
{
// アプリ起動時にJSONファイルからタスクを読み込む
repository.LoadFromFile(TaskFilePath);
Run();
// アプリ終了時にタスクを保存する
repository.SaveToFile(TaskFilePath);
}
// Runやその他のメソッドはこれまで通り...
}
C#起動時読み込み・終了時保存の流れ
- 起動時
LoadFromFileを呼び出して、tasks.jsonがあれば中身を読み込み、 なければ空の状態でスタートします。 - 終了時
Run()が終わったタイミングでSaveToFileを呼び出し、 現在のタスク一覧をtasks.jsonに書き出します。
この2つを入れるだけで、
アプリを閉じてもタスクが残る
という、かなり「本物感」のある挙動になります。
例:JSON保存付きタスク管理アプリの全体イメージ
すべてを書くと長くなるので、 JSON保存に関係する部分を中心にしたイメージを示します。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.IO;
class TaskItem
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public int Priority { get; set; }
public DateTime? DueDate { get; set; }
public bool IsCompleted { get; set; }
public DateTime CreatedAt { get; set; }
}
class TaskRepository
{
private readonly List<TaskItem> _tasks = new List<TaskItem>();
private int _nextId = 1;
public TaskItem Add(string title, string description, int priority, DateTime? dueDate)
{
var task = new TaskItem
{
Id = _nextId++,
Title = title,
Description = description,
Priority = priority,
DueDate = dueDate,
IsCompleted = false,
CreatedAt = DateTime.Now
};
_tasks.Add(task);
return task;
}
public IEnumerable<TaskItem> GetAll()
{
return _tasks;
}
public TaskItem FindById(int id)
{
return _tasks.FirstOrDefault(t => t.Id == id);
}
public bool Remove(int id)
{
var task = FindById(id);
if (task == null)
{
return false;
}
_tasks.Remove(task);
return true;
}
// JSONファイルに保存
public void SaveToFile(string filePath)
{
var options = new JsonSerializerOptions
{
WriteIndented = true
};
string json = JsonSerializer.Serialize(_tasks, options);
File.WriteAllText(filePath, json);
}
// JSONファイルから読み込み
public void LoadFromFile(string filePath)
{
if (!File.Exists(filePath))
{
// ファイルがない場合は何もしない
return;
}
string json = File.ReadAllText(filePath);
var tasks = JsonSerializer.Deserialize<List<TaskItem>>(json);
if (tasks == null)
{
return;
}
_tasks.Clear();
_tasks.AddRange(tasks);
if (_tasks.Any())
{
_nextId = _tasks.Max(t => t.Id) + 1;
}
else
{
_nextId = 1;
}
}
}
class Program
{
static TaskRepository repository = new TaskRepository();
const string TaskFilePath = "tasks.json";
static void Main()
{
// 起動時に読み込み
repository.LoadFromFile(TaskFilePath);
Run();
// 終了時に保存
repository.SaveToFile(TaskFilePath);
}
static void Run()
{
while (true)
{
Console.WriteLine("=== タスク管理アプリ ===");
Console.WriteLine("1: タスク追加");
Console.WriteLine("2: タスク一覧");
Console.WriteLine("0: 終了");
Console.Write("選択してください: ");
string input = Console.ReadLine();
switch (input)
{
case "1":
CreateTask();
break;
case "2":
ShowTasks();
break;
case "0":
Console.WriteLine("終了します。");
return;
default:
Console.WriteLine("不正な入力です。");
break;
}
Console.WriteLine();
}
}
static void CreateTask()
{
Console.WriteLine("=== タスク追加 ===");
Console.Write("タスク名を入力してください: ");
string title = Console.ReadLine();
Console.Write("詳細(メモ)を入力してください(空でも構いません): ");
string description = Console.ReadLine();
Console.Write("優先度を入力してください (1: 高, 2: 中, 3: 低): ");
int priority = int.Parse(Console.ReadLine() ?? "2");
Console.Write("期限を入力してください (yyyy-MM-dd、未入力なら期限なし): ");
string dueText = Console.ReadLine();
DateTime? dueDate = null;
if (!string.IsNullOrWhiteSpace(dueText) && DateTime.TryParse(dueText, out DateTime d))
{
dueDate = d;
}
var task = repository.Add(title, description ?? string.Empty, priority, dueDate);
Console.WriteLine($"タスクを追加しました。ID: {task.Id}");
}
static void ShowTasks()
{
Console.WriteLine("=== タスク一覧 ===");
var tasks = repository.GetAll().ToList();
if (!tasks.Any())
{
Console.WriteLine("タスクが登録されていません。");
return;
}
foreach (var t in tasks)
{
string status = t.IsCompleted ? "[完了]" : "[未完了]";
string priorityText = t.Priority switch
{
1 => "高",
2 => "中",
3 => "低",
_ => "不明"
};
string dueText = t.DueDate.HasValue
? t.DueDate.Value.ToString("yyyy-MM-dd")
: "期限なし";
Console.WriteLine($"ID: {t.Id}");
Console.WriteLine($" 状態 : {status}");
Console.WriteLine($" 優先度 : {priorityText}");
Console.WriteLine($" 期限 : {dueText}");
Console.WriteLine($" タイトル: {t.Title}");
Console.WriteLine($" 詳細 : {t.Description}");
Console.WriteLine();
}
}
}
C#このサンプルは最小限の機能だけですが、 JSON保存の流れを体験するには十分な形になっています。
セキュリティ・安全性の視点を少しだけ
天才プログラマー兼セキュリティスペシャリストっぽく、 少しだけ「安全性」の話もしておきます。
- ファイルパスの扱い 実際のアプリでは、ユーザーごとに保存場所を変えたり、 OSごとの適切なフォルダ(ドキュメントフォルダなど)を使うこともあります。
- JSONの中身の検証 本格的なアプリでは、 読み込んだJSONが壊れていないか、 想定外の値が入っていないかをチェックすることも大切です。
- 機密情報の保存 パスワードや個人情報など、 機密性の高い情報をJSONに平文で保存するのは危険です。 そういった場合は暗号化や専用のストレージを検討する必要があります。
今回のタスク管理アプリは学習用なので、 そこまで厳しく考える必要はありませんが、 「データを保存する」という行為には、 こうしたセキュリティの視点もついて回るんだな、 と頭の片隅に置いておいていただけると、 今後の成長にきっと役立ちます。
Day 78 のまとめ
Day 78 では、タスク管理アプリに JSON保存機能を追加しました。
- JSONとは何かをざっくり理解し
System.Text.Jsonを使って- タスク一覧をJSON文字列にシリアライズし
- JSONファイルからタスク一覧をデシリアライズし
- アプリ起動時に読み込み、終了時に保存する流れを作り
_nextIdを調整して、IDの一貫性を保つようにしました。
これで、タスク管理アプリは、
「その場限りの練習用」から 「継続して使える自分専用ツール」
へと、一段階レベルアップしたと言ってよい状態になりました。
ぜひ、いくつかタスクを登録してからアプリを終了し、 もう一度起動してみてください。 tasks.json を開いて中身を眺めてみるのも、とても良い学びになります。
自分が書いたコードが、 ちゃんと「記憶」を持って動いている―― その感覚は、きっと少し誇らしくて、 そして次のステップに進みたくなる、 そんなワクワクを連れてきてくれるはずです。
