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

C# 在版本/格式之间迁移序列化Json.NET文档的策略,c#,json.net,versioning,C#,Json.net,Versioning,我正在使用Json.Net序列化一些应用程序数据。当然,应用程序规范略有变化,我们需要重构一些业务对象数据。将以前序列化的数据迁移到新的数据格式有哪些可行的策略 例如,假设我们最初有一个业务对象,如: public class Owner { public string Name {get;set;} } public class LeaseInstrument { public ObservableCollection<Owner> OriginalLessees

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

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

public class Owner
{
    public string Name {get;set;} 
}
public class LeaseInstrument
{
    public ObservableCollection<Owner> OriginalLessees {get;set;}
}
我的意思是,不开玩笑,Json.Net,这就是为什么我在反序列化这些对象时尝试运行JsonConverter,以便手动处理序列化类型与编译类型不匹配的事实

值得一提的是,以下是我们正在使用的JsonSerializerSettings:

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

您有以下问题:

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

    也就是说,要处理现有的JSON,您可以调整
    IgnoreArrayTypeConverter
    from以用于可调整大小的集合:

    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();
        }
    }
    
  • 它们使用以下扩展:

    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;
        }
    }
    
    如果您不想在数据模型中依赖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;
        }
    }
    
    公共类WritablePropertiesOnlyResolver:DefaultContractResolver
    {
    受保护的重写JsonProperty CreateProperty(MemberInfo成员、MemberSerialization MemberSerialization)
    {
    var result=base.CreateProperty(成员,成员序列化);
    if(typeof(LeaseInstrument).IsAssignableFrom(result.DeclaringType)和&typeof(ICollection).IsAssignableFrom(result.PropertyType))
    {
    var converter=new IgnoreCollectionTypeConverter{ItemConverterType=typeof(OwnerToLeaseOwnerConverter)};
    结果转换器=结果转换器??转换器;
    result.MemberConverter=result.MemberConverter??converter;
    }
    返回结果;
    }
    }
    

    顺便说一句,您可能希望

    您可能会发现我们的库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次迁移到一个复杂对象,该对象在几年内发生了变化

    你能提供一些关于你的JSON字符串的例子吗?谢谢你的建议和信息!粗略地看,这似乎能解决我们的问题。明天,当我有一些带宽来解决这个问题时,我会让你知道它是否有效,并相应地标记你的答案。再次感谢。我添加了:if(objectType==typeof(LeaseOwner)){var thiscoverter=serializer.converts.Single(x=>x.GetType()==typeof(OwnerToLeaseOwnerConverter));serializer.converter.Remove(thiscoverter);var obj=serializer.Deserialize(reader);serializer.converts.Add(thiscoverter);return obj;}位于ReadJson方法顶部附近,用于处理所有者已被转换的情况。这看起来很笨重,我觉得我错过了一些明显的东西。有什么建议吗?(抱歉,我无法将其格式化)@D.Reagan-不确定我是否在跟踪。。。等等,您是否将
    OwnerToLeaseOwnerConverter
    添加到转换器的全局列表中,而不是添加到需要它的特定属性中?因为你发现的原因,那是行不通的。这真的有必要吗?@D.Reagan=还有,什么是
    LeaseOriginalOwner
    ?这不在你的问题之内。你的问题似乎变得越来越复杂,把你的问题分解成单独的小问题会更好吗?主页上有一个完整的例子,单元测试用例可用
    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;
        }
    }
    
    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;
       }
    }