【C#】標準機能でJSON をシリアライズ、デシリアライズする

追記:

最新の .NET 環境では 【C#】System.Text.Jsonでオブジェクトのシリアライズ・デシリアライズ - PG日誌 が推奨されています。こちらはやや古いバージョンの.NET向けの情報になります。

C#でJSON形式を外部ライブラリを使用しないでシリアライズ・デシリアライズする方法の紹介です。標準機能「DataContractJsonSerializer」を使用します。有力な3rdライブラリの「Json.NET」は使用しません。

.NET Frameworkの標準ライブラリにあるDataContractJsonSerializer を使ってファイルやネットワークから読みとった JSON な文字列を C# のオブジェクトへ変換したり、オブジェクトの内容を JSON 文字列へ変換を行います。

データ構造

今回シリアライズ、デシリアライズするデータとクラスの構成は以下の通りです。

扱う JSON データ

扱うJSONの形式は以下の通りです。

通常の数字、文字列、配列、リスト、ハッシュマップの5種類の変換を以下内容で検証します。

[
    {
        "id": 0,
        "name": "Taka",
        "numbers": [
            0,
            1,
            2,
            3
        ],
        "list": [
            0,
            1,
            2,
            3
        ],
        "map": [
            {
                "Key": "key1",
                "Value": "value1"
            },
            {
                "Key": "key2",
                "Value": "value2"
            },
            {
                "Key": "key3",
                "Value": "value3"
            }
        ]
    },
    {
        "id": 1,
        "name": "PG",
        "numbers": [
            10,
            11,
            12,
            13
        ],
        "list": [
            10,
            11,
            12,
            13
        ],
        "map": [
            {
                "Key": "keyAA",
                "Value": "valueAA"
            },
            {
                "Key": "keyBB",
                "Value": "valueBB"
            },
            {
                "Key": "keyCC",
                "Value": "valueCC"
            }
        ]
    }
]

クラス構成

JSONに対応するクラスの定義をします。

using System;
using System.Collections.Generic;
using System.Runtime.Serialization;

[DataContract]
public class Person
{
    [DataMember(Name = "id")]
    public int ID { get; set; }

    [DataMember(Name = "name")]
    public string Name { get; set; }

    // 配列
    [DataMember(Name = "numbers")]
    public int[] Numbers { get; set; }

    // リスト型
    [DataMember(Name = "list")]
    public List<int> NumberList { get; private set; } = new List<int>();

    // ハッシュマップ型
    [DataMember(Name = "map")]
    public IDictionary<string, string> Attributes { get; private set; } = new Dictionary<string, string>();
}

準備

先ずはプロジェクトとコードに以下準備を行います。

(1) DataContractJsonSerializerを使用するにプロジェクトにSystem.Runtime.Serializationの参照を追加します。(.NET Coreでは必要ありません

(2) 使用するコードに以下using宣言を追加します。

using System.Runtime.Serialization;

(3) シリアル化対象のクラスに以下属性を指定します。

[DataContract]
public class Person

(4) 対象メンバーに以下属性を指定します。

[DataMember]
public int ID { get; set; }

補足

もしクラスと JSON でフィールドの名前が違う場合、属性にNameが指定できます。

クラス名と異なる場合

[DataContract(Name = "Taka")]
public class Person

メンバー名が異なる場合

[DataMember(Name = "key")]
public int ID { get; set; }

Note:

クラスと JSON でフィールドの名前が違う場合、属性にNameが指定できます。

シリアライズ

オブジェクト → JSON の変換です。

DataContractJsonSerializerとMemoryStreamを使ってオブジェクトを JSON にシリアライズします。

少し複雑ですが JsonReaderWriterFactory を使うと DataContractJsonSerializer が作成する JSON をインデントできます。 useIndent へ true を指定すると文字列がインデントされた状態で出力できます。

using System.IO;
using System.Runtime.Serialization.Json;
using System.Text;

public static class JsonUtility
{
    // 指定したオブジェクトをJSONに変換する
    public static string SerializeJsonToString<T>(T src, bool useIndent = false)
    {
        using (var ms = new MemoryStream())
        {
            var settings = new DataContractJsonSerializerSettings()
            {
                UseSimpleDictionaryFormat = true, // Dictionary を「"key" : "value"」形式で出力するための指定
            };
            var serializer = new DataContractJsonSerializer(typeof(T), settings);

            if (useIndent)
            {
                // インデント付きで出力するための指定
                using (var writer = 
                    JsonReaderWriterFactory.CreateJsonWriter(ms, Encoding.UTF8, false, true, "  "))
                {
                    serializer.WriteObject(writer, src);
                    writer.Flush(); // ★忘れずに
                }
            }
            else
            {
                serializer.WriteObject(ms, src); // インデント無し
            }

            using (var sr = new StreamReader(ms))
            {
                ms.Position = 0;
                return sr.ReadToEnd();
            }
        }
    }
}

使い方

データを先ほど作成したクラスに対し設定してシリアライズを行います。

static void Main(string[] args)
{
    // データの作成
    var p_1 = new Person()
    {
        ID = 0,
        Name = "Taka",
    };

    p_1.Numbers = new int[] { 0, 1, 2, 3 };

    p_1.NumberList.Add(0);
    p_1.NumberList.Add(1);
    p_1.NumberList.Add(2);
    p_1.NumberList.Add(3);

    p_1.Attributes.Add("key1", "value1");
    p_1.Attributes.Add("key2", "value2");
    p_1.Attributes.Add("key3", "value3");

    var p_2 = new Person()
    {
        ID = 1,
        Name = "PG",
    };

    p_2.Numbers = new int[] { 10, 11, 12, 13 };

    p_2.NumberList.Add(10);
    p_2.NumberList.Add(11);
    p_2.NumberList.Add(12);
    p_2.NumberList.Add(13);

    p_2.Attributes.Add("keyAA", "valueAA");
    p_2.Attributes.Add("keyBB", "valueBB");
    p_2.Attributes.Add("keyCC", "valueCC");

    // Personのリストを作成する
    var pList = new List<Person>() { p_1, p_2 };

    // リストをデシリアライズ
    string json = JsonUtility.Serialize(pList);

    // 内容をコンソールへ表示(ファイルへの保存は別途行う
    Console.WriteLine(json);
}

これで冒頭の JSON の例と同じ値が出力されます。

デシリアライズ

今度はデシリアライズです。

using System.IO;
using System.Runtime.Serialization.Json;
using System.Text;

public static class JsonUtility
{
    /// <summary>
    /// Jsonメッセージをオブジェクトへデシリアライズします。
    /// </summary>
    public static T Deserialize<T>(string message)
    {
        using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(message)))
        {
            //var setting = new DataContractJsonSerializerSettings()
            //{
            //    UseSimpleDictionaryFormat = true,
            //};
            var serializer = new DataContractJsonSerializer(typeof(T)/*, setting*/);
            return (T)serializer.ReadObject(stream);
        }
    }
}

使い方

ファイルに保存されているJSON → オブジェクトに変換します。

static void Main(string[] args)
{
    // ファイルから読み取る
    string path = @"C:\Users\Taka\Desktop\sample.json";
    string body = File.ReadAllText(path);

    // デシリアライズ
    IList<Person> pDeserializedList = JsonUtility.Deserialize<IList<Person>>(body);

    // 内容の出力
    foreach (Person p in pDeserializedList)
    {
        Console.WriteLine("ID = " + p.ID);
        Console.WriteLine("Name = " + p.Name);
        foreach (KeyValuePair<string, string> att in p.Attributes)
        {
            Console.WriteLine(att.Key + " = " + att.Value);
        }
    }
}

DataContractJsonSerializerSettingsについて

DataContractJsonSerializerSettingsのUseSimpleDictionaryFormatは指定方法によってDictionaryのデータ形式の取り扱い方法が若干変わります。それぞれ動作を確認した結果は以下に載せておきます。

UseSimpleDictionaryFormat = trueの場合

trueの場合、任意のキーと任意のValueがDictionaryの値になります。

[
    {
        "ID": 0,
        "Name": "Taka",
        "Attributes": {
            "key1": "value1",
            "key2": "value2",
            "key3": "value3"
        }
    },
    {
        "ID": 1,
        "Name": "PG",
        "Attributes": {
            "keyAA": "valueAA",
            "keyBB": "valueBB",
            "keyCC": "valueCC"
        }
    }
]

UseSimpleDictionaryFormat = falseの場合

false(もしくは設定しない)の場合、Key="", Value=""のペアの繰り返しの配列扱いになります。多分こっちはあんまり使わないのですが、状況によってどうするか変わると思います。

[
    {
        "ID": 0,
        "Name": "Taka",
        "Attributes": [
            {
                "Key": "key1", 
                "Value": "value1"
            }, 
            {
                "Key": "key2", 
                "Value": "value2"
            }, 
            {
                "Key": "key3", 
                "Value": "value3"
            }
        ]
    },
    {
        "ID": 1, 
        "Name": "PG",
        "Attributes": [
            {
                "Key": "keyAA", 
                "Value": "valueAA"
            },
            {
                "Key": "keyBB", 
                "Value": "valueBB"
            },
            {
                "Key": "keyCC", 
                "Value": "valueCC"
            }
        ],
    }
]

コード全体

上記コード全体をGistにアップアップしました。 良かったら見てください。

JsonUtility.cs · GitHub

新しいやり方

冒頭にも気書きましたが、.NET の比較的新しいバージョンで以下の方法が使用できます。こちらはパフォーマンス的にやや問題があるので可能であれば以下をご検討ください。

takap-tech.com