白黒羊

【Unity】NewtonSoftのJSONConverterを便利に使う

辞書のキーに自作クラスを使ってシリアライズする

のは難しいらしい。
辞書の代わりにList<KeyValuePair<TK, TV>> を使うと良い感じにシリアライズできます。

List<KeyValuePair<MyClass, string>> 

SingleOrArrayConverter

1つ以上の値が入る(けど多くの場合は1つしか入らない)フィールドを持つJSONデータを考えます。

{
  [
    {
      "name": "apple",
      "color": [
                 "red",
                 "green"
               ]
    },
    {
      "name": "banana",
      "color": [
                 "yellow"
               ]
    },
    {
      "name": "grape",
      "color": [
                 "purple"
               ]
    }
  ]
}

ほとんどのcolorフィールドにはbananagrapeのようにひとつしか値が入らないとすると、このデータを下のように書くことができたら嬉しいです。

{
  [
    {
      "name": "apple",
      "color": [
                 "red",
                 "green"
               ]
    },
    {
      "name": "banana",
      "color": "yellow"
    },
    {
      "name": "grape",
      "color": "purple"
    }
  ]
}

JSONConverter ではSerialize/Deserializeの方法をカスタマイズすることができます。
まず、下記のようなJsonConverterを作成します。

using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Project.Scripts.Utility
{
    public class SingleOrArrayConverter<T> : JsonConverter
    {
        public override bool CanWrite => false;

        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(List<T>);
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
            JsonSerializer serializer)
        {
            var token = JToken.Load(reader);
            return token.Type == JTokenType.Array
                ? token.ToObject<List<T>>()
                : new List<T> { token.ToObject<T>() };
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            var list = (List<T>)value;
            if (list.Count == 1) value = list[0];
            serializer.Serialize(writer, value);
        }
    }
}

これを使ってJsonを読み取ってあげます。適用したいfieldに対して [JsonConverter(typeof(SingleOrArrayConverter))] をつけます。

public record Fruit
{
    public readonly string Name;
    public readonly IEnumerable<string> Colors;

    [JsonConstructor]
    public Fruit(
        [JsonProperty("name")] string name,
        [JsonProperty("color")][JsonConverter(typeof(SingleOrArrayConverter<string>))] IEnumerable<string> colors)
    {
        Name = name;
        Colors = colors;
    }
}

Deserializeするときは普通です。

var asset = Resources.Load<TextAsset>(path);
var data = JsonConvert.DeserializeObject<IEnumerable<Fruit>>(asset.text).ToList();

参考

https://stackoverflow.com/questions/24504245/not-ableto-serialize-dictionary-with-complex-key-using-json-net/56351540

https://stackoverflow.com/questions/18994685/how-to-handle-both-a-single-item-and-an-array-for-the-same-property-using-json-n

関連