Quantcast
Channel: かずきのBlog@hatena
Viewing all articles
Browse latest Browse all 1387

Json.NET で enum を文字列で保存したり数字で保存したりするものを混在させたい

$
0
0

Json.NET は便利ですよね。ということで、こういう感じのクラスを…

publicenum Size
{
    Small = 100,
    Large = 1000,
}

publicenum TargetType
{
    None,
    TypeA,
    TypeB,
    TypeC,
}

publicclass Target
{
    publicstring Name { get; set; }
    public Size Size { get; set; }
    public TargetType Type { get; set; }
}

Size プロパティは数字としてシリアライズしつつ、Type プロパティは enum の文字列としてシリアライズしたい…!というケースがあるとします。Json.NET には、StringEnumConverter というクラスがあるので、これを指定すれば enum を文字列としてシリアライズしてくれるようになります。

JsonConvert.SerializeObject, JsonConvert.DeserializeObject あたりでこれを指定すると Size プロパティが数字じゃなくなってしまうのでプロパティ単位で指定する必要があります。これは JsonConverterAttribute でやるのがいい感じです。

using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System;

namespace ConsoleApp1
{
    class Program
    {
        staticvoid Main(string[] args)
        {
            var target = new Target
            {
                Name = "Tanaka",
                Size = Size.Large,
                Type = TargetType.TypeA,
            };

            var json = JsonConvert.SerializeObject(target);
            Console.WriteLine(json);

            var deserializedTarget = JsonConvert.DeserializeObject<Target>(json);
            Console.WriteLine($"{deserializedTarget.Name}, {deserializedTarget.Size}");
        }
    }

    publicenum Size
    {
        Small = 100,
        Large = 1000,
    }

    publicenum TargetType
    {
        None,
        TypeA,
        TypeB,
        TypeC,
    }

    publicclass Target
    {
        publicstring Name { get; set; }
        public Size Size { get; set; }
        [JsonConverter(typeof(StringEnumConverter))]
        public TargetType Type { get; set; }
    }
}

このように、シリアライズ/デシリアライズのときの挙動をカスタマイズしたいプロパティにだけ JsonConverterAttribute を指定すれば OK です。

属性で汚染されたくない…!

あっ、はい。 Json.NET の属性は付けれないときもありますよね。自分が変更できないクラスとか。

JsonConverter で頑張る

JsonConverter で Target オブジェクトのシリアライズ/デシリアライズの方法をカスタマイズする方法です。 ちょっとメンドクサイ…。

ただただ、純粋にメンドクサイ。

ContractResolver で頑張る

要は特定のプロパティだけ後から JsonConverter つけれればいいので… DefaultContractResolver あたりを継承して GetProperty メソッドをオーバーライドして、狙ったプロパティのときだけ Converter を差し込んでやるとう作戦です。

using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using System;
using System.Reflection;

namespace ConsoleApp1
{
    class Program
    {
        staticvoid Main(string[] args)
        {
            var target = new Target
            {
                Name = "Tanaka",
                Size = Size.Large,
                Type = TargetType.TypeA,
            };

            var json = JsonConvert.SerializeObject(target, new JsonSerializerSettings
            {
                ContractResolver = new MyContractResolver(),
            });
            Console.WriteLine(json);

            var deserializedTarget = JsonConvert.DeserializeObject<Target>(json, new JsonSerializerSettings
            {
                ContractResolver = new MyContractResolver(),
            });
            Console.WriteLine($"{deserializedTarget.Name}, {deserializedTarget.Size}");
        }
    }

    publicclass MyContractResolver : DefaultContractResolver
    {
        protectedoverride JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
        {
            var p = base.CreateProperty(member, memberSerialization);
            if (member.DeclaringType == typeof(Target) && member.Name == nameof(Target.Type))
            {
                p.Converter = new StringEnumConverter();
            }

            return p;
        }
    }

    publicenum Size
    {
        Small = 100,
        Large = 1000,
    }

    publicenum TargetType
    {
        None,
        TypeA,
        TypeB,
        TypeC,
    }

    publicclass Target
    {
        publicstring Name { get; set; }
        public Size Size { get; set; }
        public TargetType Type { get; set; }
    }
}

enum を辞める

こういう風にしてしまう手もありますね。 Java に enum が無かったころに編み出された enum パターン風に。

using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using System;
using System.Linq;
using System.Reflection;

namespace CustomJson
{
    class Program
    {
        staticvoid Main(string[] args)
        {
            var target = new Target
            {
                Name = "Tanaka",
                Size = Size.Large,
                Type = TargetType.TypeA,
            };

            var json = JsonConvert.SerializeObject(target, new SizeJsonConverter(),
                new StringEnumConverter());
            Console.WriteLine(json);

            var deserializedTarget = JsonConvert.DeserializeObject<Target>(json, new SizeJsonConverter(), new StringEnumConverter());
            Console.WriteLine($"{deserializedTarget.Name}, {deserializedTarget.Size.Value}");
        }
    }

    publicclass SizeJsonConverter : JsonConverter
    {
        publicoverridebool CanConvert(Type objectType)
        {
            returntypeof(Size) == objectType;
        }

        publicoverrideobject ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            var jToken = JToken.Load(reader);
            intvalue = jToken.Value<int>();
            returntypeof(Size).GetTypeInfo()
                .GetProperties(BindingFlags.Static | BindingFlags.Public)
                .Where(x => x.PropertyType == typeof(Size))
                .Select(x => (Size)x.GetValue(null))
                .First(x => x.Value == value);
        }

        publicoverridevoid WriteJson(JsonWriter writer, objectvalue, JsonSerializer serializer)
        {
            var size = (Size)value;
            writer.WriteValue(size.Value);
        }
    }

    publicclass Target
    {
        publicstring Name { get; set; }
        public Size Size { get; set; }
        public TargetType Type { get; set; }
    }

    publicsealedclass Size
    {
        publicstatic Size Large { get; } = new Size(1000);
        publicstatic Size Small { get; } = new Size(100);
        private Size(intvalue) { this.Value = value; }
        publicint Value { get; }
    }

    publicenum TargetType
    {
        None, TypeA, TypeB, TypeC
    }
}

Size を enum パターン的に作ってしまって、Size クラスに対して JsonConverter を書く感じです。これならどうだろう?

まとめ

おとなしくクラスに JsonConverter を指定しましょう。


Viewing all articles
Browse latest Browse all 1387

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>