はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【Unity】System.Text.JsonでUnityEngine.Colorのシリアライズ・デシリアライズに対応する

はじめに

今回はUnityEngine.ColorSystem.Text.Jsonでシリアライズ・デシリアライズできるようにする方法を紹介したいと思います。

learn.microsoft.com

環境

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を継承したクラスを作成し、ReadWriteの挙動を記述します。また今回は[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を定義してあげれば対応できます。