.NET Core 3.0 から利用可能な新しい標準ライブラリ JSON シリアライザー System.Text.Json
の JsonSerializer
使い方の紹介です。
動作環境
以下環境で標準で使用することができます。
- 使用可能な.NETのバージョン
- .NET Core3.0~
- .NET Standard 2.1~
- .NET Framework 4.8~
使い方
早速使い方の紹介です。
まず以下の名前空間を using します。
using System.Text.Json;
using System.Text.Json.Serialization;
今回は以下のようなJSONデータを扱います。
ルート要素をオブジェクト配列として扱います。
[
{
"id": 0,
"name": "Taka",
"numbers": [ 0, 1, 2, 3 ],
"list": [ 0, 1, 2, 3 ],
"map": {
"key1": "value1",
"key2": "value2",
"key3": "value3",
"key4": "value4",
"key5": "value5"
}
},
{
"id": 1,
"name": "PG",
"numbers": [ 10, 11, 12, 13 ],
"list": [10, 20, 30],
"map": {
"aaa": "111",
"bbb": "222",
"ccc": "333"
}
}
]
次に、シリアライズ・デシリアライズするクラスを宣言します。JSON に対応するクラスを作成し、そこに JSON データを当てはめていきます。
public class JsonItem
{
[JsonPropertyName("id")]
public int ID { get; set; }
[JsonPropertyName("asdf")]
public string sample = "";
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("numbers")]
public int[] Numbers { get; set; }
[JsonPropertyName("list")]
public List<float> NumberList { get; private set; } = new List<float>();
[JsonPropertyName("map")]
public IDictionary<string, string> Attributes { get; private set; }
= new Dictionary<string, string>();
[JsonIgnore]
public string PrivacyInfo { get; set; }
}
var items = new List<JsonItem>();
1点注意があります。System.Text.Json のシリアライズ対象はプロパティのため、フィールドはシリアライズできません(このため Unity で JsonUtility を使用してる場合の乗り換えは難しいかもしれません)
プロパティ名と JSON 内のノードの名異なる場合「JsonPropertyName (System.Text.Json.Serialization名前空間)」を上記コード例のように付与します。また、デフォルトでは自動的にプロパティが全部出力されるのでシリアライズ対象にしたくないプロパティには「JsonIgnore」を同じく付与します。経験上、プロパティ名 == ノード名だったとしてもJsonPropetyName は指定しておいたほうが無難です。
リスト も Dictionary も配列もすべてサポートしているので一般的な .NET の型はほぼすべてカバーしています。ただし入れ子になったオブジェクト階層は 64までなので深すぎるオブジェクトだけ注意しましょう。
シリアライズ:文字列 → オブジェクト
シリアライズ方法は以下の通りです。
List<JsonItem> items = SampleData.GetJsonItems(DataLength);
var op = new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.Create(UnicodeRanges.All),
WriteIndented = true,
IgnoreNullValues = true,
IgnoreReadOnlyProperties = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
string jsonStr = JsonSerializer.Serialize(item, op);
必要に応じてコード例のコメントの通りオプションを指定します。日本語を含むマルチバイト文字を扱う場合 Encoder を指定します。他のオプションは状況に応じて指定します。他のオプションについては公式のリファレンスを参照してください。
JSON化したデータは以下のようなデータとして jsonStr 変数に入っています。
// jsonStr 変数の中身
[
{
"time": "2020-08-21T00:14:58.0850867+09:00",
"span": "10200",
"id": 1905413078,
"name": "2d9f9caa-9f05-4213-a992-3e5f6672c575",
"numbers": [
1512193737,
300677771,
1625274804,
1946273800,
251410219
],
"list": [
263971600,
1.634365E+09,
550383800,
1.4658991E+09,
203363660
],
"map": {
"feb9c436-6e85-453b-98ce-395f549d7b2e": "b1763a54-c41c-4b46-b424-cebf87271469",
"c23bf690-33ae-4096-998d-0901b09e4ec9": "ef737ce1-5185-4e57-a009-edd538c7ae57",
"44598508-d4e1-44d8-89a3-4ce3ce241215": "c33f3104-3a8e-46fc-90cb-8d9fcb0952de",
"4b1a697c-c17e-4458-9e61-ec0f222df2cf": "4bc7eac1-3160-4496-8667-dc11e67979e6",
"c1d3f90f-64ba-4f9e-a43b-bf97b20d144c": "47fd7ae3-a194-46c4-b299-e60bc9d83ea2"
}
}
]
参考までにデータ生成は以下の通り処理を行っています。
public static class SampleData
{
private static readonly Random r = new Random();
public static List<JsonItem> GetJsonItems(int count)
{
IEnumerable<JsonItem> f()
{
for (int i = 0; i < count; i++)
{
var item = new JsonItem()
{
ID = r.Next(),
Name = Guid.NewGuid().ToString(),
Numbers = new int[]
{
r.Next(),
r.Next(),
r.Next(),
r.Next(),
r.Next(),
},
};
item.NumberList.Add(r.Next());
item.NumberList.Add(r.Next());
item.NumberList.Add(r.Next());
item.NumberList.Add(r.Next());
item.NumberList.Add(r.Next());
item.Attributes[Guid.NewGuid().ToString()] = Guid.NewGuid().ToString();
item.Attributes[Guid.NewGuid().ToString()] = Guid.NewGuid().ToString();
item.Attributes[Guid.NewGuid().ToString()] = Guid.NewGuid().ToString();
item.Attributes[Guid.NewGuid().ToString()] = Guid.NewGuid().ToString();
item.Attributes[Guid.NewGuid().ToString()] = Guid.NewGuid().ToString();
yield return item;
}
}
return f().ToList();
}
}
デシリアライズ:オブジェクト → 文字列
デシリアライズの方法は以下の通りです。
先ほどの jsonStr をデシリアライズすると元の List に戻すことができます。
op = new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = true,
ReadCommentHandling = JsonCommentHandling.Skip,
AllowTrailingCommas = true,
};
List<JsonItem> list = JsonSerializer.Deserialize<List<JsonItem>>(jsonStr, op);
これもオプションを指定することができます。
標準に準拠しないJSONをどう扱うかなどのオプションが用意されています。
ストリーム(ファイル)に読み書きする
書き込み方は以下の通りです。
var options = new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
};
using var stream = new FileStream(@"c:\tmp\sample.json", FileMode.Create, FileAccess.Write);
await JsonSerializer.SerializeAsync(stream
items,
op);
読み込み方法は以下の通り
var options = new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.Create(UnicodeRanges.All),
};
using var stream = new FileStream(@"c:\tmp\sample.json", FileMode.Open, FileAccess.Read);
var itemList =
await JsonSerializer.DeserializeAsync<List<JsonItem>>(stream, options);
その他の規則
DateTime 、TimeSpan の扱い
以下、標準の状態で DateTime と TimeSpan の変換を見てみます。
[JsonPropertyName("time")]
public DateTime Time { get; set; } = DateTime.Now;
[JsonPropertyName("span")]
public TimeSpan Span { get; set; } = TimeSpan.FromSeconds(10.2);
TimeSpan 型は変換できないみたいです。
派生クラスのシリアライズ
派生クラスの場合以下のようにひと手間必要です。ちょっと違和感がありますがお約束だと思ってあきらめましょう。
public class A { }
public class B : A { }
B b = new B();
JsonSerializer.Serialize(b);
JsonSerializer.Serialize<object>(b);
カスタムコンバーターを使う
上記で出力されない型を独自の規則で入出力するには自作の Converter を実装する必要があります。
先ほどシリアライズできなかった TimeSpan 型をシリアライズ・デシリアライズできるようにするためには以下のようにコンバーターを実装します。
public class TimeSpanConverter : JsonConverter<TimeSpan>
{
public override TimeSpan
Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return TimeSpan.FromMilliseconds(double.Parse(reader.GetString()));
}
public override void
Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.TotalMilliseconds.ToString());
}
}
で、プロパティに以下のように記述します。
[JsonConverter(typeof(TimeSpanConverter))]
public TimeSpan Span { get; set; }
他にもいろいろ変換方法を指定する方法はありますがこれだけ覚えておけば大抵大丈夫だと思います(他にもやり方があるということだけ覚えておいてこれで対応できそうもなければここまで記事を見ていればリファレンスを参照すれば十分対応可能かと思います)
DateTime 型は表現方法が色々あるので標準でなくカスタムコンバーターを記述することもありそうです。
既存のシリアライザーとの動作速度の比較
最後に手元で適当に計測したら速度は以下の通りです。
System.Text.Json > Json.NET >> DataContractSerializer
実行速度のおおまかな比率はシリアライズもデシリアライズも大凡以下の通りです。
DataContractSerializer を 「1」 とした場合、JSON.NET「1.2~1.5 倍速い」、System.Text.Json「2.1~2.9 倍速い」
新規開発の場合とりあえず System.Text.Json をシリアライザとして選択するのがよさそうです。MSDN リファレンスに Newtonsoft.Jsonから移行する方法という移行のサポートページが公開されているため一読するとよさそうです。
関連記事
もう使うことは無いかもしれませんが、これ以前のオブジェクトのシリアライズの方法については以下の通りです。
takap-tech.com