CodeGym /コース /C# SELF /単純なコレクションのシリアライズ: List<...

単純なコレクションのシリアライズ: List<T>, T[]

C# SELF
レベル 46 , レッスン 0
使用可能

1. はじめに

コレクションはもう扱ったことがあるよね。プログラミングでも日常でもコレクションがないと結構不便。例えば一日のやることリストを作るとき:買い物、先生に電話、注文を受け取る、ってそれぞれ別々のノートを作るより1つのリストにまとめたほうが楽で合理的だよね。

プログラミングでも同じで、ユーザーや注文やメッセージが増えてきたら、いちいち変数を大量に作るんじゃなくてコレクションを使う。List や Dictionary、Set を使えばグループのデータをまとめて保存・走査・処理できる。

実用シナリオ:

  • アプリ間でデータを保存・受け渡す: 本のコレクションをあるプログラムから別のプログラムへ移したり。
  • データセットをディスクにキャッシュする。
  • ネットワーク越しにデータを送る(frontend ↔ backend)。
  • インポート/エクスポート(たとえば自分の本のデータを JSON にエクスポートする機能を作るとか)。

面接で聞かれたら:「注文のリストをどうシリアライズして保存する?」って聞かれたら、単一オブジェクトだけでなくコレクションのシリアライズについても触れられると良いよ。

2. シリアライズ時のコレクションの振る舞い

JSON とコレクション:いい相性

普通のオブジェクトをシリアライズすると JSON オブジェクト { "field": value } になるよね。一方でリストや配列をシリアライズすると JSON の配列 [ ... ] になる。

見た目でいうと:

C# JSON
List<int> { 1, 2, 3 }
[1, 2, 3]
Book[]
[{"Title":"A","Author":"B"}, ...]
List<Book>
[{"Title":"A"}, {"Title":"B"}]

キーの魔法: コレクションに対して JsonSerializer.Serialize() を呼ぶだけで自動的に配列になるよ。逆に Deserialize<List<T>>() を呼べば元に戻る。

C# のコレクションと JSON 配列の対応

コレクションの型(C#) JSON
Book[]
new Book[] { ... }
[ { ... }, { ... } ]
List<Book>
new List<Book> { ... }
[ { ... }, { ... } ]
int[]
new int[] { 1, 2, 3 }
[1, 2, 3]
List<string>
new List<string> { "a", "b" }
["a", "b"]
List<List<Book>>
new List<List<Book>> { ... }
[ [ { ... } ], [ { ... } ] ]

3. 例:本の配列のシリアライズとデシリアライズ

まず最小限の例から。前の講義で使った Book クラスを使おう:

public class Book
{
    public string Title { get; set; }
    public string Author { get; set; }
}

次に本の配列を作ってシリアライズして、ファイルに保存して読み戻してみるよ。

using System;
using System.IO;
using System.Text.Json;

namespace LibraryApp
{
    public class Book
    {
        public string Title { get; set; }
        public string Author { get; set; }
    }

    class Program
    {
        static void Main()
        {
            // 本の配列を作る
            Book[] books = new Book[]
            {
                new Book { Title = "三人の仲間", Author = "エーリッヒ・マリア・レマルク" },
                new Book { Title = "巨匠とマルガリータ", Author = "ミハイル・ブルガーコフ" },
                new Book { Title = "1984", Author = "ジョージ・オーウェル" }
            };

            // 本の配列を JSON 文字列にシリアライズする
            var options = new JsonSerializerOptions { WriteIndented = true };	
            string json = JsonSerializer.Serialize(books, options);
            Console.WriteLine("本のJSON配列:\n" + json);

            // ファイルに書き込む(同期)
            File.WriteAllText("books.json", json);

            // ファイルから読む(同期)
            string jsonFromFile = File.ReadAllText("books.json");

            // 配列にデシリアライズする
            Book[]? booksFromFile = JsonSerializer.Deserialize<Book[]>(jsonFromFile);

            // 結果をコンソールに出す
            Console.WriteLine("\nデシリアライズされた本:");
            if (booksFromFile != null)
            {
                foreach (var book in booksFromFile)
                {
                    Console.WriteLine($"- {book.Title} (作者: {book.Author})");
                }
            }
        }
    }
}

ここで何が起きているの?

  • まず Book[] の配列を作って3冊分入れている。
  • JsonSerializer.Serialize で配列をきれいな JSON 文字列にしている(オプションの WriteIndented は読みやすくするため)。
  • それをファイルに書いて、読み戻してデシリアライズすればまた配列が戻ってくる。
  • 復元したデータが正しく戻っているか確認している。

プログラムのフォルダに "books.json" ができていることを確認してね。中身はだいたいこんな JSON になるよ:

[
  {
    "Title": "三人の仲間",
    "Author": "エーリッヒ・マリア・レマルク"
  },
  {
    "Title": "巨匠とマルガリータ",
    "Author": "ミハイル・ブルガーコフ"
  },
  {
    "Title": "1984",
    "Author": "ジョージ・オーウェル"
  }
]

4. 例:List<T> コレクションのシリアライズ

List<Book> の扱いはほとんど同じ。シリアライザはコレクションを見て JSON の配列にしてくれるよ。

List<Book> myBooks = new List<Book>
{
    new Book { Title = "罪と罰", Author = "ドストエフスキー" },
    new Book { Title = "戦争と平和", Author = "レフ・トルストイ" }
};

string jsonList = JsonSerializer.Serialize(myBooks, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(jsonList);

// で、こうやってデシリアライズする:
List<Book>? loadedBooks = JsonSerializer.Deserialize<List<Book>>(jsonList);

// 全部戻ってきたか確認!
foreach (var book in loadedBooks!)
{
    Console.WriteLine($"{book.Title} ({book.Author})");
}

整数のリストだけをシリアライズできる?

もちろん!クラスだけじゃなくプリミティブ型のリストもシリアライズできるよ:

List<int> numbers = new List<int> { 10, 20, 30, 40 };
string jsonNums = JsonSerializer.Serialize(numbers); // 結果: [10,20,30,40]
List<int>? loadedNums = JsonSerializer.Deserialize<List<int>>(jsonNums);
// loadedNums: 同じ値を持つ List<int>

5. 図とアナロジー:シリアライザはコレクションをどう見る?

コレクションのシリアライズの仕組みをイメージしやすくするために簡単な図を見てみよう:

graph TD;
    A[C# の List[Book]] -->|Serialize| B[文字列の JSON 配列]
    B -->|Write| C[ファイル books.json]
    C -->|Read| D[ファイルからの JSON 文字列]
    D -->|Deserialize| E[C# の List[Book]]

処理の短い説明:

  • コレクションや配列は JSON の配列([ ... ])としてシリアライズされる。
  • 要素の順序は維持される(特別な設定をしない限り)。
  • コレクションの各要素はそれぞれシリアライズされる。

6. コレクションシリアライズの注意点

1. null と空のコレクション

コレクションが null の場合、シリアライザはデフォルトで JSON に null を書くよ。業務ロジック上で空のコレクションと null が違う意味になることがあるから注意してね。

コレクションが空(new List<Book>())なら JSON は [] — 空の配列になる。要素が無いことを明示するにはこれが便利。

2. デシリアライズ時は順序が重要

配列の要素順は常に保持される。だから同じ順序でシリアライズしたら同じ順序で戻ってくるよ。

3. 異なる型のオブジェクトが混ざるコレクション?

System.Text.Json はデフォルトではポリモーフィズムをサポートしてない。例えば List<Animal> の中に Dog や Cat(派生クラス)が混ざっている場合、どの実際の型かをそのまま復元することはできない。ポリモーフィックなシリアライズは別途工夫が必要だけど、普通のリストなら問題ないよ。

7. よくある間違いと勘違い

ミス1:コレクションをシリアライズしていて、内部に private フィールドがあることを忘れる

JsonSerializer は公開プロパティ(getter/setter を持つ public なプロパティ)だけシリアライズする。例えばこんなクラスだと:

public class User
{
    public string Login { get; set; }

    private string Password { get; set; }  // シリアライズされないよ!
}

パスワードは JSON に入らない。セキュリティ的には良い挙動だけど、保存したければプロパティを public にするなど設計を見直してね。

ミス2:コレクション内に null 要素がある場合のシリアライズ

コレクションに null が含まれていると例えば new List<Book> { null, book2 } のような場合、シリアライズ結果の最初の要素は null になる。デシリアライズしても同じ位置に null が復元されるよ。例:

[null, { "Title": "戦争と平和", "Author": "レフ・トルストイ" }]

実務ではあまり見ないケースだけど、要素に「穴」が開く可能性があるならロジックで対処してね。

ミス3:デシリアライズ時の型が違う

よくある単純なミス:シリアライズは List でやったのにデシリアライズを配列にしてしまう(またはその逆)。互換性がある場合(Book[]List<Book>)は動くけど、例えば文字列の配列をオブジェクトにデシリアライズしようとするとエラーになることがあるので型に注意してね。

コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION