はじめに
今回はUnityEngine.Color
をSystem.Text.Json
でシリアライズ・デシリアライズできるようにする方法を紹介したいと思います。
環境
Unity2023.1.17f1
MacBook Pro 2023 16inch, Apple M2 Pro
System.Text.Json v6.0.0-preview
概要
System.Text.Json
では リフレクション もしくは SourceGenerator
を用いたソース生成 の2種類によってシリアライズ・デシリアライズ時に利用するメタデータを収集します。
既定では、System.Text.Json ではリフレクションを使用して、実行時にシリアル化と逆シリアル化のためにオブジェクトのプロパティにアクセスする目的で必要なメタデータを収集します。 別の方法として、System.Text.Json では、C# のソース生成機能を使用して、パフォーマンスを向上させ、プライベート メモリの使用量を削減し、アセンブリのトリミングを容易にすることができます。これにより、アプリのサイズが小さくなります。
C# を使用した JSON のシリアル化と逆シリアル化 - .NET | Microsoft Learn
ただしこれらの手法を単に利用するだけではUnityEngine.Color
をシリアライズ・デシリアライズすることができません。
var value = Color.red; // リフレクションを利用した場合はJsonExceptionが投げられる var json = JsonSerializer.Serialize(value); // SourceGeneratorを利用した場合はInvalidOperationExceptionが投げられる var json2 = JsonSerializer.Serialize(value, ColorSourceGenerationContext.Default.Color); var json3 = JsonSerializer.Serialize(value, typeof(Color), ColorSourceGenerationContext.Default);
[JsonSerializable(typeof(Color))] internal partial class ColorSourceGenerationContext : JsonSerializerContext { }
しかし自身でカスタムコンバーターを記述することにより、UnityEngine.Color
に対応することができます。
learn.microsoft.com
その方法を紹介します。
コード
JsonConverter
を継承したクラスを作成し、Read
・Write
の挙動を記述します。また今回は[0,1,2,3]
のようにRGBA
を配列として並べることとしました。
↓実装側
/// <summary> /// <see cref="UnityEngine.Color" />をシリアライズ・デシリアライズするための<see cref="JsonConverter{T}" />です。 /// </summary> public class ColorConverter : JsonConverter<Color> { public override Color Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType != JsonTokenType.StartArray) { throw new JsonException(); } reader.Read(); if (reader.TokenType != JsonTokenType.Number) { throw new JsonException(nameof(Color.r)); } var r = reader.GetSingle(); reader.Read(); if (reader.TokenType != JsonTokenType.Number) { throw new JsonException(nameof(Color.b)); } var g = reader.GetSingle(); reader.Read(); if (reader.TokenType != JsonTokenType.Number) { throw new JsonException(nameof(Color.b)); } var b = reader.GetSingle(); reader.Read(); if (reader.TokenType != JsonTokenType.Number) { throw new JsonException(nameof(Color.a)); } var a = reader.GetSingle(); reader.Read(); if (reader.TokenType != JsonTokenType.EndArray) { throw new JsonException(); } return new Color(r, g, b, a); } public override void Write(Utf8JsonWriter writer, Color value, JsonSerializerOptions options) { writer.WriteStartArray(); writer.WriteNumberValue(value.r); writer.WriteNumberValue(value.g); writer.WriteNumberValue(value.b); writer.WriteNumberValue(value.a); writer.WriteEndArray(); } }
↓利用側
// UnityEngine.ColorのSerialize var color = Color.white; var json = JsonSerializer.Serialize(color, new JsonSerializerOptions { Converters = { new ColorConverter() } }); // [1,1,1,1] Console.WriteLine(json);
// UnityEngine.ColorのDeserialize const string json = "[1,1,1,1]"; var color = JsonSerializer.Deserialize<Color>(json, new JsonSerializerOptions() { Converters = { new ColorConverter() } }); // Color.white Console.WriteLine(color);
案外実装自体はシンプルですね。カスタムコンバーターの詳細をもっと知りたい方は是非公式ドキュメントを読んでみてください。
learn.microsoft.com
ちなみに今回の実装は基本パターンでの実装で、それ以外にファクトリパターンもあります。
- 基本パターン : 非ジェネリック型およびクローズ ジェネリック型用
- ファクトリパターン : Enum 型またはオープン ジェネリック型用
またJsonExeption
についてですが、ちょっと面白い性質があったりします。
メッセージなしで JsonException をスローする場合、シリアライザーがそのエラーの原因となった JSON の部分へのパスを含むメッセージを作成します。
JSON シリアル化のためのカスタム コンバーターを作成する方法 - .NET | Microsoft Learn
下手にメッセージを書くよりも、内容を生成してもらった方が良いのかもしれませんね。
さいごに
UnityEngine.Color
に限らずとも、いろいろな型を自身でJsonConverter
を定義してあげれば対応できます。