アプリケーションのデータを JSON にシリアライズして保存する手順を整理しておきます。
JSON は人間が読めて、差分管理もしやすく、他言語・他環境でも扱いやすいので、設定やユーザーデータの永続化に向いています。ただし「なんでも JSON に突っ込む」だけだと、将来の変更で壊れたり、サイズや速度で困ったりするので、最低限の設計と運用の型を作っておくのが大事です。
初期の .NET Framework の頃は XML が標準的なファイル形式だったようですが、最近の .NET は JSON に移行してきているとか、いないとか…?
例題
日付と数量のペアを、日付の名前のJSONファイルに保存します。
喩えるなら「何月何日の売上個数」みたいなデータですね。
2026-02-16.json
{
"Date": "2026-02-16",
"Total": 73799
}
DTO を用意する
アプリ内部のクラスをそのまま保存形式にすると、内部実装の変更が保存形式に波及して壊れやすくなります。 なので、保存用に DTO(Data Transfer Object) を用意して、「保存に必要な最低限の形」を決めます。
public sealed class StatsStore
{
private sealed class DailyStatsDto
{
public string Date { get; set; } = string.Empty;
public long Total { get; set; }
}
保存先ディレクトリを定義する
以下のディレクトリを保存先とします:
C:\User\<username>\AppData\Roaming\<appName>\stats
private readonly string _baseDir;
public string StatsDirectory => _baseDir;
public StatsStore(string appName)
{
// コンストラクタで保存先ディレクトリをセットします。
var root = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
_baseDir = System.IO.Path.Combine(root, appName, "stats");
Directory.CreateDirectory(_baseDir);
}
JSON ファイルに保存する
シリアライズは DTO オブジェクトを渡すだけ。
using System.Text.Json;
public void SaveTotal(DateOnly date, long total)
{
var path = GetPath(date);
var dto = new DailyStatsDto
{
Date = date.ToString("yyyy-MM-dd"),
Total = total
};
var json = JsonSerializer.Serialize(dto, JsonOptions);
File.WriteAllText(path, json);
}
JSON ファイルから読み出す
アンシリアライズはジェネリックに DTO クラスを渡すだけ。
public long LoadTotal(DateOnly date)
{
var path = GetPath(date);
if (!File.Exists(path)) return 0;
try
{
var json = File.ReadAllText(path);
var dto = JsonSerializer.Deserialize<DailyStatsDto>(json);
return dto?.Total ?? 0;
}
catch
{
return 0; // 読み取りエラー時はゼロを返す
}
}
クラス実装
public sealed class StatsStore
{
private sealed class DailyStatsDto
{
public string Date { get; set; } = string.Empty;
public long Total { get; set; }
}
private readonly string _baseDir;
private static readonly JsonSerializerOptions JsonOptions = new()
{
WriteIndented = true
};
public string StatsDirectory => _baseDir;
public StatsStore(string appName)
{
var root = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
_baseDir = System.IO.Path.Combine(root, appName, "stats");
Directory.CreateDirectory(_baseDir);
}
public long LoadTotal(DateOnly date)
{
var path = GetPath(date);
if (!File.Exists(path))
{
return 0;
}
try
{
var json = File.ReadAllText(path);
var dto = System.Text.Json.JsonSerializer.Deserialize<DailyStatsDto>(json);
return dto?.Total ?? 0;
}
catch
{
return 0; // 読み取りエラー時はゼロを返す
}
}
public void SaveTotal(DateOnly date, long total)
{
var path = GetPath(date);
var dto = new DailyStatsDto
{
Date = date.ToString("yyyy-MM-dd"),
Total = total
};
var json = JsonSerializer.Serialize(dto, JsonOptions);
File.WriteAllText(path, json);
}
private string GetPath(DateOnly date)
=> Path.Combine(_baseDir, $"{date:yyyy-MM-dd}.json");
}
テストコード
// 永続化ストア
StatsStore store = new StatsStore("SampleJson");
// 2025年12月1日から31日までのダミーデータを保存
var startDate = new DateOnly(2025, 12, 1);
var endDate = new DateOnly(2025, 12, 31);
Console.WriteLine($"Saving data from {startDate} to {endDate}...");
for (var date = startDate; date <= endDate; date = date.AddDays(1))
{
// ダミーデータ: 日付に基づいた値を生成(例: 10000 + 日 * 1000)
long total = 10000 + date.Day * 1000;
store.SaveTotal(date, total);
Console.WriteLine($"Saved {date}: {total}");
}
Console.WriteLine("\nLoading some samples:");
Console.WriteLine("2025-12-01 total: " + store.LoadTotal(new DateOnly(2025, 12, 1)));
Console.WriteLine("2025-12-15 total: " + store.LoadTotal(new DateOnly(2025, 12, 15)));
Console.WriteLine("2025-12-31 total: " + store.LoadTotal(new DateOnly(2025, 12, 31)));
まとめ
System.Text.Json.JsonSerializer が良い仕事をしてくれていますね。
発展
1. 指定期間の平均値を求める
/// <summary>
/// start〜end(両端含む)に存在する日だけ平均。0日なら null。
/// </summary>
public double? ComputeAverage(DateOnly start, DateOnly end)
{
if (end < start) return null;
var totals = new List<long>();
for (var d = start; d <= end; d = d.AddDays(1))
{
var path = GetPath(d);
if (!File.Exists(path)) continue;
var t = LoadTotal(d);
totals.Add(t);
}
if (totals.Count == 0) return null;
return totals.Average(x => (double)x);
}
テストコード
var avg1 = store.ComputeAverage(
new DateOnly(2025, 12, 1),
new DateOnly(2025, 12, 10));
Console.WriteLine($"Average (2025-12-01 to 2025-12-10): {avg1}");
2. 列挙可能コレクションを返す
/// <summary>
/// foreachで回せるようにする
/// </summary>
public IEnumerable<(DateOnly Date, long Total)>
LoadDailyTotals(DateOnly from, DateOnly to)
{
if (to < from) yield break;
for (var d = from; d <= to; d = d.AddDays(1))
yield return (d, LoadTotal(d));
}
テストコード
var dailyData = store.LoadDailyTotals(
new DateOnly(2025, 12, 1),
new DateOnly(2025, 12, 7));
foreach (var (date, total) in dailyData)
{
Console.WriteLine($"{date}: {total}");
}
参考サイト
JsonSerializer Class (System.Text.Json) | Microsoft Learn
https://learn.microsoft.com/en-us/dotnet/api/system.text.json.jsonserializer?view=net-10.0