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

时间:2015-12-08 17:40:14

标签: c# json.net versioning

我正在使用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
    };

2 个答案:

答案 0 :(得分:4)

您有以下问题:

  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(); } } 以使用可调整大小的集合:

    Owner
  2. 您需要将LeaseOwner升级为PreserveReferencesHandling = PreserveReferencesHandling.Objects

    您可以为此目的编写make Json.NET ignore $type if it's incompatible,将JSON的相关部分加载到JsonConverter,然后检查该对象是否与旧数据模型中的对象相似,或者是新对象。如果JSON看起来很旧,请使用JObject根据需要映射字段。如果JSON对象看起来很新,那么只需Linq to JSON即可。

    由于您要设置"$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;
        }
    }
    
  3. 这些使用扩展名:

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

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

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

    如果您不希望在数据模型中依赖Json.NET,可以在自定义合约解析程序中执行此操作:

    TestExceptionHandling.java:12: error: exception StupidException is never thrown in body of corresponding try statement
                    catch (StupidException stupidEx) {
                    ^
    

    顺便提一下,您可能需要JsonConverterAttribute

答案 1 :(得分:1)

您可能会发现我们的库 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次迁移到一个已经过几年更改的复杂对象。