如何以重构安全的方式使用JSON.Net来持久化数据

时间:2014-06-12 08:25:10

标签: c# json refactoring migration json.net

我使用JSON.Net来保存从接口继承的对象集合。我发现我可以将TypeNameHandling设置为

JsonSerializerSettings.TypeNameHandling = TypeNameHandling.Auto;

我可以获得注入到发射的json中的对象的完全限定类型名称,可以在序列化中使用它来构造正确的对象。

"$type": "Newtonsoft.Json.Samples.Hotel, Newtonsoft.Json.Tests"

然而,我非常满意我的resharper重构,这种映射会在代码版本之间快速破解。

我想要的不是存储完全限定的类型名称,而是将每个类分配一个Guid作为类型ID,它在重构时永远不会改变,并使用它来映射回反序列化代码。

这是某人以前尝试过的事情,如果是这样的话怎么去做?

1 个答案:

答案 0 :(得分:2)

修改

我们现在有一个开源项目来帮助迁移。

https://github.com/Weingartner/Migrations.Json.Net

原始回答

这是我写的解决我的问题的JsonConverter。我所做的是使用 Guid 属性(通常用于com interop)标记我的序列化。然后我提供了一个基于guid的工厂。该工厂基于ninject DI框架。

关键的代码行在ReadJson方法

var document = _Kernel.Get<IWeinCadDocument>(typeId.ToString());

这里我根据Guid字符串创建了一个正确类型的文档实例。使用以下功能预先注册类型。

public void BindDocumentTypes(params Type[] types)
{
    Debug.Assert
        (types.All(p => typeof (IWeinCadDocument).IsAssignableFrom(p)));
     foreach (var documentType in types)
    {
        Kernel.Bind<IWeinCadDocument>()
              .To(documentType)
              .Named(documentType.GUID.ToString());
    }
}

可以通过以下方式使用它们的Guid注册一组类型。

BindDocumentTypes(
    typeof (ADocument), typeof (BDocument), typeof (CDocument)
);

为了确保Guid不会在软件版本之间发生变化甚至编译,我们会使用属性强制执行Guid。

[Guid("4882176A-751A-4153-928A-915BEA87FAB3")]
public class ADocument : WeinCadDocumentBase<ADocument>
{
    public ADocument( IWeinCadDocumentStorage storage )
        : base(storage)
    {
    }

    public override object PersistentData
    {
        get
        {
            return new DocumentData(10, 20);
        }
    }


}

完整的JSonConverter在下面。

public class WeinCadDocumentConverter : JsonConverter
{
    private readonly IKernel _Kernel;

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var document = value as IWeinCadDocument;
        Debug.Assert(document != null, "document != null");

        AssertThatWeHaveACustomGuidSet(value);

        writer.WriteStartObject();
        writer.WritePropertyName("InstanceId");
        writer.WriteValue(document.InstanceId);
        writer.WritePropertyName("TypeId");
        writer.WriteValue(document.GetType().GUID);
        writer.WritePropertyName("Name");
        writer.WriteValue(document.Name);
        writer.WritePropertyName("Data");
        serializer.Serialize(writer, document.PersistentData);

        writer.WriteEndObject();
    }

    /// <summary>
    /// The object need a custom GuidAttribute to be set on the class otherwise the
    /// GUID may change between versions of the code or even runs of the applications.
    /// This Guid is used for identifying types from the document store and calling
    /// the correct factory. 
    /// </summary>
    /// <param name="value"></param>
    private static void AssertThatWeHaveACustomGuidSet(object value)
    {
        var attr = System.Attribute.GetCustomAttributes(value.GetType())
                         .Where(a => a is GuidAttribute)
                         .ToList();

        if (attr.Count == 0)
            throw new ArgumentException
                (String.Format
                     (@"Type '{0}' does not have a custom GuidAttribute set. Refusing to serialize.",
                      value.GetType().Name),
                 "value");
    }

    public override object ReadJson
        (JsonReader reader,
         Type objectType,
         object existingValue,
         JsonSerializer serializer)
    {
        JObject json = JObject.Load(reader);
        var props = json.Properties().ToList();
        var instanceId = (Guid) props[0].Value;
        var typeId = (Guid) props[1].Value;
        var name = (string) props[2].Value;
        var data = props[3].Value;

        var document = _Kernel.Get<IWeinCadDocument>(typeId.ToString());

        document.PersistentData = data;
        document.InstanceId = instanceId;
        document.Name = name;

        return document;
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof (IWeinCadDocument).IsAssignableFrom(objectType);
    }

    public WeinCadDocumentConverter(IKernel kernel)
    {
        _Kernel = kernel;
    }
}