2017-02-10 3 views
1

Использование Json.NET, я хотел бы, чтобы отобразить JObject на объект .NET со следующим поведением:Заполнить объект, в котором объекты повторно используются, и массивы заменяются?

  1. Для (ненулевым) свойств массива в JObject, замените всю коллекцию на целевом объекте.

  2. Для свойств объекта (не нуль) JObject, повторно используйте свойство целевого объекта, если оно не является нулевым, и на нем отображаются только предоставленные свойства.

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-to-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 Я заметил, что вызов 'serializer.Deserialize (reader, objectType)' в 'ReadJson' вызывает меня в бесконечном цикле. Довольно легко понять, почему. Считаете ли вы, что использование '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

Спасибо за подробный ответ. Вероятно, эффективность достигается за счет того, что я сделал, хотя отражение и т. Д. Делает более сложным решение. Силл взвешивает мои варианты, но еще раз спасибо. –

Смежные вопросы