Json.NETを使って任意のプロパティのみをシリアライズ/デシリアライズする方法

.NETのオブジェクトのある時点の特定のプロパティの状態だけを保存したり復元したりしたい要件があったので、Json.NET(Newtonsoft.Json)のDefaultContractResolverを使って実現してみました。


サンプルソースコード(C#)

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

// ① スナップショットを取得するブジェクトを作成
var sourceData = new DataClass
{
    StringValue = "Parent",
    BoolValue = true,
    IntValue = int.MaxValue,
    DoubleValue = double.MaxValue,
    DecimalValue = decimal.MaxValue,
    NestedDataClass = new DataClass
    {
        StringValue = "Child",
        DecimalValue = 1234567890.1234567890M
    }
};

// ② ①のスナップショットをJSONで取得
Snapshot.Store(sourceData, out string? json);

// ③ スナップショットを復元するオブジェクトを作成
var restoreData = new DataClass();

// ④ ③に対して②のスナップショットを復元
Snapshot.Restore(json!, restoreData);

// 待機用
Console.ReadLine();

// ----------------- 以下クラス定義 -----------------
// スナップショット操作クラス
internal static class Snapshot
{
    private static readonly JsonSerializerSettings jss = new()
    {
        Formatting = Formatting.Indented,
        ContractResolver = new SnapshotContractResolver(),
    };

    // スナップショットを保存
    public static bool Store<T>(T source, out string? json)
    {
        json = null;
        try
        {
            json = JsonConvert.SerializeObject(source, jss);
            return true;
        }
        catch (Exception e)
        {
            Debug.WriteLine(e.Message);
        }
        return false;
    }

    // スナップショットを復元
    public static bool Restore<T>(string json, T target) where T : notnull
    {
        try
        {
            JsonConvert.PopulateObject(json, target);
            return true;
        }
        catch (Exception e)
        {
            Debug.WriteLine(e.Message);
        }
        return false;
    }

    // シリアル化ルールクラス
    private class SnapshotContractResolver : DefaultContractResolver
    {
        protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
        {
            var props = base.CreateProperties(type, memberSerialization);
            // SnapshotProperty属性のあるプロパティのみシリアライズ対象とする
            return props.Where(prop =>
            {
                var atrbs = prop.AttributeProvider?.GetAttributes(typeof(SnapshotPropertyAttribute), true);
                return atrbs != null && atrbs.Any();
            }).ToList();
        }

        protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
        {
            var prop = base.CreateProperty(member, memberSerialization);
            // プロパティが読み取り/書き込み可能な場合のみシリアライズ対象とする
            prop.ShouldSerialize = instance =>
            {
                var propInfo = member as PropertyInfo;
                if (propInfo != null && propInfo.CanRead && propInfo.CanWrite)
                {
                    try
                    {
                        propInfo.GetValue(instance, null);
                        return true;
                    }
                    catch (Exception e)
                    {
                        Debug.WriteLine(e.Message);
                    }
                }
                return false;
            };
            return prop;
        }
    }
}

// データクラス
internal class DataClass
{
    [SnapshotProperty]
    public string? StringValue { get; set; }

    [SnapshotProperty]
    public bool? BoolValue { get; set; }

    [SnapshotProperty]
    public int? IntValue { get; set; }

    [SnapshotProperty]
    public double? DoubleValue { get; set; }

    [SnapshotProperty]
    public decimal? DecimalValue { get; set; }

    [SnapshotProperty]
    public string? ReadOnlyValue
    {
        get => "ReadOnly";
    }

    [SnapshotProperty]
    public string? WriteOnlyValue
    {
        set { }
    }

    [SnapshotProperty]
    public string? NotImplementedValue
    {
        get => throw new NotImplementedException();
        set { }
    }

    [SnapshotProperty]
    public DataClass? NestedDataClass { get; set; }

    public string? Dummy { get; set; } = "dummy";
}

// スナップショットプロパティ属性
[AttributeUsage(AttributeTargets.Property, Inherited = true)]
internal class SnapshotPropertyAttribute : Attribute
{
}


説明

状態を保存/復元したいデータクラスのプロパティには予めSnapshotPropertyカスタム属性を付与しておきます。当該プロパティが読み取り可能かつ書き込み可能な場合のみプロパティがシリアライズ対象となり、復元時にはJSONにシリアライズされているプロパティのみを既存のオブジェクトに対して反映(更新)します。

① 状態を保存したいオブジェクト(sourceData)
file

② 取得した状態(json)
file

③ 状態復元対象のオブジェクト(restoreData)
file

④ 状態復元後のオブジェクト(restoreData)
file


参考ウェブサイトなど


以上です。

シェアする

  • このエントリーをはてなブックマークに追加

フォローする