在 versions/formats 之间迁移序列化 Json.NET 文档的策略

Strategies for migrating serialized Json.NET document between versions/formats

我正在使用 Json.Net 序列化一些应用程序数据。当然,应用程序规范略有变化,我们需要重构一些业务对象数据。将以前序列化的数据迁移到我们的新数据格式有哪些可行的策略?

例如,假设我们最初有一个业务对象,如:

public class Owner
{
    public string Name {get;set;} 
}
public class LeaseInstrument
{
    public ObservableCollection<Owner> OriginalLessees {get;set;}
}

我们使用 Json.Net 将 LeaseInstrument 的一个实例序列化到一个文件中。现在,我们将业务对象更改为:

public class Owner
{
   public string Name {get;set;}
}
public class LeaseOwner
{
  public Owner Owner { get;set;}
  public string DocumentName {get;set;}
}
public class LeaseInstrument
{
    public ObservableCollection<LeaseOwner> OriginalLessees {get;set;}
}

我研究过为 LeaseInstrument 编写自定义 JsonConverter,但是 ReadJson 方法从未被命中...而是在反序列化器到达该点之前抛出异常:

Additional information: Type specified in JSON
'System.Collections.ObjectModel.ObservableCollection`1[[BreakoutLib.BO.Owner,
BreakoutLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]],
System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
is not compatible with 'System.Collections.ObjectModel.ObservableCollection`1[[BreakoutLib.BO.LeaseOwner, BreakoutLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'. Path 'Is.$values[8].OriginalLessors.$type', line 3142, position 120.

我的意思是,不是开玩笑,Json.Net,这就是为什么我在反序列化这些对象时尝试 运行 JsonConverter,这样我就可以手动处理序列化类型不存在的事实匹配编译类型!!

值得一提的是,这里是我们正在使用的 JsonSerializerSettings:

var settings = new JsonSerializerSettings
    {
      PreserveReferencesHandling = PreserveReferencesHandling.Objects,
      ContractResolver = new WritablePropertiesOnlyResolver(),
      TypeNameHandling = TypeNameHandling.All,
      ObjectCreationHandling = ObjectCreationHandling.Reuse
    };

您有以下问题:

  1. 您使用 TypeNameHandling.All 进行了序列化。此设置序列化集合和对象的类型信息。我不建议这样做。相反,我建议使用 TypeNameHandling.Objects,然后让反序列化系统选择集合类型。

    也就是说,要处理您现有的 JSON,您可以调整 中的 IgnoreArrayTypeConverter 以与可调整大小的集合一起使用:

    public class IgnoreCollectionTypeConverter : JsonConverter
    {
        public IgnoreCollectionTypeConverter() { }
    
        public IgnoreCollectionTypeConverter(Type ItemConverterType) 
        { 
            this.ItemConverterType = ItemConverterType; 
        }
    
        public Type ItemConverterType { get; set; }
    
        public override bool CanConvert(Type objectType)
        {
            // TODO: test with read-only collections.
            return objectType.GetCollectItemTypes().Count() == 1 && !objectType.IsDictionary() && !objectType.IsArray;
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (!CanConvert(objectType))
                throw new JsonSerializationException(string.Format("Invalid type \"{0}\"", objectType));
            if (reader.TokenType == JsonToken.Null)
                return null;
            var token = JToken.Load(reader);
            var itemConverter = (ItemConverterType == null ? null : (JsonConverter)Activator.CreateInstance(ItemConverterType, true));
            if (itemConverter != null)
                serializer.Converters.Add(itemConverter);
    
            try
            {
                return ToCollection(token, objectType, existingValue, serializer);
            }
            finally
            {
                if (itemConverter != null)
                    serializer.Converters.RemoveLast(itemConverter);
            }
        }
    
        private static object ToCollection(JToken token, Type collectionType, object existingValue, JsonSerializer serializer)
        {
            if (token == null || token.Type == JTokenType.Null)
                return null;
            else if (token.Type == JTokenType.Array)
            {
                // Here we assume that existingValue already is of the correct type, if non-null.
                existingValue = serializer.DefaultCreate<object>(collectionType, existingValue);
                token.PopulateObject(existingValue, serializer);
                return existingValue;
            }
            else if (token.Type == JTokenType.Object)
            {
                var values = token["$values"];
                if (values == null)
                    return null;
                return ToCollection(values, collectionType, existingValue, serializer);
            }
            else
            {
                throw new JsonSerializationException("Unknown token type: " + token.ToString());
            }
        }
    
        public override bool CanWrite { get { return false; } }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }
    
  2. 您需要将 Owner 升级到 LeaseOwner

    你可以用它写一个JsonConverter for this purpose that loads the relevant portion of JSON into a JObject, then checks to see whether the object looks like one from the old data model, or the new. If the JSON looks old, map fields as necessary using Linq to JSON. If the JSON object looks new, you can just populate your LeaseOwner

    由于您正在设置 PreserveReferencesHandling = PreserveReferencesHandling.Objects,转换器将需要手动处理 "$ref" 属性:

    public class OwnerToLeaseOwnerConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return typeof(LeaseOwner).IsAssignableFrom(objectType);
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.TokenType == JsonToken.Null)
                return null;
            var item = JObject.Load(reader);
            if (item["$ref"] != null)
            {
                var previous = serializer.ReferenceResolver.ResolveReference(serializer, (string)item["$ref"]);
                if (previous is LeaseOwner)
                    return previous;
                else if (previous is Owner)
                {
                    var leaseOwner = serializer.DefaultCreate<LeaseOwner>(objectType, existingValue);
                    leaseOwner.Owner = (Owner)previous;
                    return leaseOwner;
                }
                else
                {
                    throw new JsonSerializationException("Invalid type of previous object: " + previous);
                }
            }
            else
            {
                var leaseOwner = serializer.DefaultCreate<LeaseOwner>(objectType, existingValue);
                if (item["Name"] != null)
                {
                    // Convert from Owner to LeaseOwner.  If $id is present, this stores the reference mapping in the reference table for us.
                    leaseOwner.Owner = item.ToObject<Owner>(serializer);
                }
                else
                {
                    // PopulateObject.  If $id is present, this stores the reference mapping in the reference table for us.
                    item.PopulateObject(leaseOwner, serializer);
                }
                return leaseOwner;
            }
        }
    
        public override bool CanWrite { get { return false; } }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }
    

这些使用扩展名:

public static class JsonExtensions
{
    public static T DefaultCreate<T>(this JsonSerializer serializer, Type objectType, object existingValue)
    {
        if (serializer == null)
            throw new ArgumentNullException();
        if (existingValue is T)
            return (T)existingValue;
        return (T)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
    }

    public static void PopulateObject(this JToken obj, object target, JsonSerializer serializer)
    {
        if (target == null)
            throw new NullReferenceException();
        if (obj == null)
            return;
        using (var reader = obj.CreateReader())
            serializer.Populate(reader, target);
    }
}

public static class TypeExtensions
{
    /// <summary>
    /// Return all interfaces implemented by the incoming type as well as the type itself if it is an interface.
    /// </summary>
    /// <param name="type"></param>
    /// <returns></returns>
    public static IEnumerable<Type> GetInterfacesAndSelf(this Type type)
    {
        if (type == null)
            throw new ArgumentNullException();
        if (type.IsInterface)
            return new[] { type }.Concat(type.GetInterfaces());
        else
            return type.GetInterfaces();
    }

    public static IEnumerable<Type> GetCollectItemTypes(this Type type)
    {
        foreach (Type intType in type.GetInterfacesAndSelf())
        {
            if (intType.IsGenericType
                && intType.GetGenericTypeDefinition() == typeof(ICollection<>))
            {
                yield return intType.GetGenericArguments()[0];
            }
        }
    }

    public static bool IsDictionary(this Type type)
    {
        if (typeof(IDictionary).IsAssignableFrom(type))
            return true;

        foreach (Type intType in type.GetInterfacesAndSelf())
        {
            if (intType.IsGenericType
                && intType.GetGenericTypeDefinition() == typeof(IDictionary<,>))
            {
                return true;
            }
        }
        return false;
    }
}

public static class ListExtensions
{
    public static bool RemoveLast<T>(this IList<T> list, T item)
    {
        if (list == null)
            throw new ArgumentNullException();
        var comparer = EqualityComparer<T>.Default;
        for (int i = list.Count - 1; i >= 0; i--)
        {
            if (comparer.Equals(list[i], item))
            {
                list.RemoveAt(i);
                return true;
            }
        }
        return false;
    }
}

您可以使用 JsonConverterAttribute 将转换器直接应用于您的数据模型,如下所示:

public class LeaseInstrument
{
    [JsonConverter(typeof(IgnoreCollectionTypeConverter), typeof(OwnerToLeaseOwnerConverter))]
    public ObservableCollection<LeaseOwner> OriginalLessees { get; set; }
}

如果您不想在您的数据模型中依赖 Json.NET,您可以在您的自定义合同解析器中执行此操作:

public class WritablePropertiesOnlyResolver : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var result = base.CreateProperty(member, memberSerialization);
        if (typeof(LeaseInstrument).IsAssignableFrom(result.DeclaringType) && typeof(ICollection<LeaseOwner>).IsAssignableFrom(result.PropertyType))
        {
            var converter = new IgnoreCollectionTypeConverter { ItemConverterType = typeof(OwnerToLeaseOwnerConverter) };
            result.Converter = result.Converter ?? converter;
            result.MemberConverter = result.MemberConverter ?? converter;
        }
        return result;
    }
}

顺便说一下,您可能想要

您可能会发现我们的图书馆 Migrations.Json.Net 有帮助

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

一个简单的例子。假设您从 class

开始
public class Person {
   public string Name {get;set}
}

然后您想迁移到

public class Person {
   public string FirstName {get;set}
   public string SecondName {get;set}
   public string Name => $"{FirstName} {SecondName}";
}

您可能会进行以下迁移

public class Person {
   public string FirstName {get;set}
   public string SecondName {get;set}
   public string Name => $"{FirstName} {SecondName}";

   public void migrate_1(JToken token, JsonSerializer s){
      var name = token["Name"];
      var names = names.Split(" ");
      token["FirstName"] = names[0];
      token["SecondName"] = names[1];
      return token;
   }
}

上面掩盖了一些细节,但在项目的主页上有一个完整的例子。我们在两个生产项目中广泛使用了它。主页上的示例有 13 次迁移到多年来发生变化的复杂对象。