2017-02-10 50 views
1

使用Json.NET,我想一個JObject映射到.NET對象與以下行爲:填充對象被重用並且數組被替換的對象?

  1. 對於(非空)的JObject的陣列性能,更換整個收集在目標對象上。

  2. 對於JObject的(非空)對象屬性,如果目標對象的屬性不爲null,則重新使用該屬性,並將提供的屬性映射到它上面。

JsonSerializer.Populate似乎是我想要的東西,如this answer描述。至於我正在尋找的行爲,似乎我可以通過JsonSerializerSettings.ObjectCreationHandling實現其中一個或另一個,但不是兩個。 ObjectCreationHandling.Replace按照需求#1做了我想要的,而ObjectCreationHandling.Auto按照需求#2做了我想要的,但它將數組項添加到現有集合中。

在這裏達到兩個要求的推薦方法是什麼?

+1

需要注意的是,JObject.Merge提供了一種很好的方式來通過[MergeArrayHandling]配置數組行爲(http://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_Linq_MergeArrayHandling.htm)。但是這是用於JObject到JObject的映射,而不是JObject-to.NET對象。不幸的是,這個設置在JsonSerializerSettings中不可用。 –

回答

2

一種修復方法是使用自定義JsonConverter,通過在檢測到集合類型時忽略現有值來有效替換集合。

public class ReplaceArrayConverter : JsonConverter 
{ 
    public override bool CanConvert(Type objectType) { 
     // check for Array, IList, etc. 
     return objectType.IsCollection(); 
    } 

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { 
     // ignore existingValue and just create a new collection 
     return JsonSerializer.CreateDefault().Deserialize(reader, objectType); 
    } 

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { 
     JsonSerializer.CreateDefault().Serialize(writer, value); 
    } 
} 

使用像這樣:

var ser = JsonSerializer.CreateDefault(new JsonSerializerSettings { 
    Converters = new[] { new ReplaceArrayConverter() } 
}); 

using (var reader = jObj.CreateReader()) { 
    ser.Populate(reader, model); 
} 
+1

其實你只需要忽略'existingValue'並返回一個新的集合。 – dbc

+0

doh ...好點!我會編輯這個。 –

+0

@dbc我注意到在'ReadJson'中調用'serializer.Deserialize(reader,objectType)'會讓我陷入無限循環。很容易明白爲什麼。你認爲使用'JsonSerializer.CreateDefault()'是否正確?它的工作原理,但不知道是否有更簡單/更高效的東西。 –

2

Json.NET將自動替換任何陣列或只讀集合。要在反序列化時清除讀寫集合,您可以創建一個custom contract resolver,將OnDeserializingCallback添加到每個可修改的集合中,以便在反序列化開始時清除集合。清除集合,而在更換它直接處理的情況下收集得到,只是,例如:

public class RootObject 
{ 
    readonly HashSet<int> hashSet = new HashSet<int>(); 
    public HashSet<int> HashSetValues { get { return this.hashSet; } } 
} 

合同解析如下:

public class CollectionClearingContractResolver : DefaultContractResolver 
{ 
    static void ClearGenericCollectionCallback<T>(object o, StreamingContext c) 
    { 
     var collection = o as ICollection<T>; 
     if (collection == null || collection is Array || collection.IsReadOnly) 
      return; 
     collection.Clear(); 
    } 

    static SerializationCallback ClearListCallback = (o, c) => 
     { 
      var collection = o as IList; 
      if (collection == null || collection is Array || collection.IsReadOnly) 
       return; 
      collection.Clear(); 
     }; 

    protected override JsonArrayContract CreateArrayContract(Type objectType) 
    { 
     var contract = base.CreateArrayContract(objectType); 
     if (!objectType.IsArray) 
     { 
      if (typeof(IList).IsAssignableFrom(objectType)) 
      { 
       contract.OnDeserializingCallbacks.Add(ClearListCallback); 
      } 
      else if (objectType.GetCollectItemTypes().Count() == 1) 
      { 
       MethodInfo method = typeof(CollectionClearingContractResolver).GetMethod("ClearGenericCollectionCallback", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); 
       MethodInfo generic = method.MakeGenericMethod(contract.CollectionItemType); 
       contract.OnDeserializingCallbacks.Add((SerializationCallback)Delegate.CreateDelegate(typeof(SerializationCallback), generic)); 
      } 
     } 

     return contract; 
    } 
} 

public static class TypeExtensions 
{ 
    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 class JsonExtensions 
{ 
    public static void Populate<T>(this JToken value, T target) where T : class 
    { 
     value.Populate(target, null); 
    } 

    public static void Populate<T>(this JToken value, T target, JsonSerializerSettings settings) where T : class 
    { 
     using (var sr = value.CreateReader()) 
     { 
      JsonSerializer.CreateDefault(settings).Populate(sr, target); 
     } 
    } 
} 

然後使用它,這樣做:

var settings = new JsonSerializerSettings 
{ 
    ContractResolver = new CollectionClearingContractResolver(), 
}; 
jObject.Populate(rootObject, settings); 

樣品fiddle

Deserialization causes copies of List-Entries所示,反序列化在默認構造函數中填充集合的對象時,此類合約解析器也很有用。

+0

感謝您的徹底解答。可能比我做的有效率提高,雖然反射等等使得更復雜的解決方案。 Sill權衡我的選擇,但再次感謝。 –