ImmutableオブジェクトのJSONシリアライズ、デシリアライズ(C#)
今回はNewtonsoft.Jsonを利用して、Immutableオブジェクトをシリアライズ、デシリアライズしてみたいと思います。
.NetにはDataContractJsonSerializerが標準で用意されていますが、DataContract、DataMember属性を付加する必要があり、動作のカスタマイズが大変そうであったため、Newtonsoft.Jsonを採用しました。
対象オブジェクト
下記のクラスをシリアライズ、デシリアライズしてみたいと思います。
FullNameは導出項目のため、シリアライズ対象から外される必要があります。
public class Person { public int Age { get; } public string FirstName { get; } public string LastName { get; } public Person Child { get; } public string FullName => FirstName + " " + LastName; public Person(int age, string firstName, string lastName, Person child) { Age = age; FirstName = firstName; LastName = lastName; Child = child; } }
var person = new Person(35, "John", "Smith", new Person(10, "Richard", "Smith", null));
シリアライズ
デフォルトでは、publicなgetterからの値をシリアライズします。
そのため、FullNameもシリアライズされてしまいます。
そこで導出項目は無視するようContractResolverを独自に定義します。
class SerializeResolver : DefaultContractResolver { protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { var p = base.CreateProperty(member, memberSerialization); var field = member.DeclaringType.GetField($"<{member.Name}>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic); // setterがなく、BackingFieldも持たないものは無視 p.Ignored = field == null && p.Writable == false; return p; } }
シリアライズは、このContractResolverを利用して下記のように記述できます。
var setting = new JsonSerializerSettings() { ContractResolver = new SerializeResolver() }; string json = JsonConvert.SerializeObject(person, setting);
問題なくシリアライズされていることが確認できます。(Visual StudioのJSONビューワで確認)
デシリアライズ
デフォルトではsetterが存在しないプロパティはデシリアライズできません。
そこでプロパティが復元できるよう、独自のContractResolverを定義します。
class DeserializeResolver : DefaultContractResolver { protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { var p = base.CreateProperty(member, memberSerialization); if (p.Writable == false) { var field = member.DeclaringType.GetField($"<{member.Name}>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic); if (field != null) { // BackingFieldが存在する場合、ValueProviderをセット p.ValueProvider = new FieldValueProvider(field); p.Writable = true; } } return p; } }
FieldValueProviderは下記のように定義できます。
class FieldValueProvider : IValueProvider { FieldInfo _field; public FieldValueProvider(FieldInfo field) => _field = field; public object GetValue(object target) => _field.GetValue(target); public void SetValue(object target, object value) => _field.SetValue(target, value); }
デシリアライズは、このContractResolverを利用して下記のように記述できます。
var setting = new JsonSerializerSettings() { ContractResolver = new DeserializeResolver() }; var person = JsonConvert.DeserializeObject<Person>(json, seting);
BackingFieldを使わない場合
BackingFieldはコンパイラが勝手に作成するFieldのため、名称が変わる可能性は0ではありません。
心配な場合は、private setを追加して独自のContractResolverを定義してください。