在版本/格式之间迁移序列化 Json.NET 文档的策略

本文关键字:NET Json 文档 策略 序列化 迁移 版本 格式 之间 | 更新日期: 2023-09-27 18:26:06

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

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

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

我们将 LeaseInstrument 的实例序列化为包含 Json.Net 的文件。 现在,我们将业务对象更改为如下所示:

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
    };

在版本/格式之间迁移序列化 Json.NET 文档的策略

您有以下问题:

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

    话虽如此,要处理现有的 JSON,如果与可调整大小的集合不兼容,则可以从 make Json.NET 忽略$type调整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,将 JSON 的相关部分加载到JObject中,然后检查对象是否看起来像旧数据模型或新数据模型的对象。 如果 JSON 看起来很旧,请根据需要使用 Linq to JSON 映射字段。 如果 JSON 对象看起来很新,则可以使用它填充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

一个简单的例子。假设您从一堂课开始

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 次迁移到一个复杂对象,该对象在几年内发生了变化。