2015-12-08 46 views
2

我正在使用Json.Net序列化一些應用程序數據。當然,應用程序規範稍有改變,我們需要重構一些業務對象數據。將先前的序列化數據遷移到我們的新數據格式有哪些可行的策略?在版本/格式之間遷移序列化的Json.NET文檔的策略

例如,假設我們已經orignally有一個業務對象,如:

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

你能提供一些關於你的JSON字符串的例子嗎? – fhelwanger

回答

1

您有以下問題:

  1. 您使用TypeNameHandling.All序列化。此設置序列化集合以及對象的類型信息。我不建議這樣做。相反,我建議使用TypeNameHandling.Objects,然後讓反序列化系統選擇集合類型。

    話雖這麼說,應對現有的JSON,你可以從make Json.NET ignore $type if it's incompatible適應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. 您需要升級你的OwnerLeaseOwner

    您可以編寫一個JsonConverter用於此目的,將JSON的相關部分加載到JObject中,然後檢查該對象是否與舊數據模型或新數據類似。如果JSON看上去很舊,則根據需要使用Linq to JSON映射字段。如果JSON對象看起來很新,那麼只需要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; 
    } 
} 

,你可能想cache your custom contract resolver for best performance.

+1

感謝您的建議和信息!匆匆一瞥,這看起來好像會解決我們的問題。明天,當我有一些帶寬來處理這個問題時,我會告訴你它是否有效,並相應地標記你的答案。 –

+0

完美無瑕地工作。再次感謝。 –

+0

我添加了: if(objectType == typeof(LeaseOwner)) { var thisconverter = serializer.Converters.Single(x => x.GetType()== typeof(OwnerToLeaseOwnerConverter)); serializer.Converters.Remove(thisconverter); var obj = serializer.Deserialize (reader); serializer.Converters.Add(thisconverter); return obj; } 靠近ReadJson方法的頂部,以處理業主已經轉換的情況。這看起來很笨重,我覺得我失去了一些明顯的東西。有小費嗎? (對不起,我無法得到這個格式) –

0

您可能會發現我們的圖書館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次遷移到一個複雜的對象,這個對象已經改變了幾年。

+0

你可以發表一個例子,說明如何做到這一點? – juunas

+0

主頁上有一個完整的例子,並提供單元測試用例https://github.com/Weingartner/Migrations.Json.Net – bradgonesurfing