2012-05-04 13 views
11

Voglio solo il primo livello di profondità di un oggetto (non voglio bambini). Sono disposto a utilizzare qualsiasi libreria disponibile. La maggior parte delle librerie genera semplicemente un'eccezione quando viene raggiunta la profondità di ricorsione, invece di ignorarla. Se questo non è possibile, c'è un modo per ignorare la serializzazione di alcuni membri dato un determinato tipo di dati?Come serializzare o deserializzare un oggetto JSON in una certa profondità in C#?

Edit: Diciamo che ho un oggetto in questo modo:

class MyObject 
{ 
    String name = "Dan"; 
    int age = 88; 
    List<Children> myChildren = ...(lots of children with lots of grandchildren); 
} 

Voglio rimuovere tutti i bambini (tipi complessi anche) per restituire un oggetto come questo:

class MyObject 
{ 
    String name = "Dan"; 
    int age = 88; 
    List<Children> myChildren = null; 
} 
+0

Potete mostrare qualche esempio di problema JSON? – igofed

+1

Puoi semplicemente creare un nuovo oggetto senza figli e serializzarlo? –

+0

Logicamente questo avrebbe senso, ma voglio spogliare qualsiasi oggetto di tipo Object dei suoi figli. Ho pensato che la serializzazione JSON sarebbe il modo migliore per farlo, ma sono decisamente aperto a suggerimenti. –

risposta

24

Questo è possibile in Json.NET usando un po 'di coordinamento tra JsonWriter e il serializzatore ContractResolver.

Un'unità personalizzata JsonWriter incrementa un contatore quando viene avviato un oggetto e quindi lo decrementa di nuovo quando finisce.

public class CustomJsonTextWriter : JsonTextWriter 
{ 
    public CustomJsonTextWriter(TextWriter textWriter) : base(textWriter) {} 

    public int CurrentDepth { get; private set; } 

    public override void WriteStartObject() 
    { 
     CurrentDepth++; 
     base.WriteStartObject(); 
    } 

    public override void WriteEndObject() 
    { 
     CurrentDepth--; 
     base.WriteEndObject(); 
    } 
} 

Un personalizzato ContractResolver applica uno speciale ShouldSerialize predicato su tutte le proprietà che verranno utilizzate per verificare la profondità attuale.

public class CustomContractResolver : DefaultContractResolver 
{ 
    private readonly Func<bool> _includeProperty; 

    public CustomContractResolver(Func<bool> includeProperty) 
    { 
     _includeProperty = includeProperty; 
    } 

    protected override JsonProperty CreateProperty(
     MemberInfo member, MemberSerialization memberSerialization) 
    { 
     var property = base.CreateProperty(member, memberSerialization); 
     var shouldSerialize = property.ShouldSerialize; 
     property.ShouldSerialize = obj => _includeProperty() && 
              (shouldSerialize == null || 
              shouldSerialize(obj)); 
     return property; 
    } 
} 

Il seguente metodo mostra come queste due classi personalizzate funzionano insieme.

Il seguente codice di test dimostra la limitazione della profondità massima ai livelli 1 e 2 rispettivamente.

var obj = new Node { 
    Name = "one", 
    Child = new Node { 
     Name = "two", 
     Child = new Node { 
      Name = "three" 
     } 
    } 
}; 
var txt1 = SerializeObject(obj, 1); 
var txt2 = SerializeObject(obj, 2); 

public class Node 
{ 
    public string Name { get; set; } 
    public Node Child { get; set; } 
} 
+0

Non riesco a farlo funzionare nella versione attuale della libreria Json.Net. Sembra che i metodi CustomContractResolvers non vengano mai chiamati. – Kjellski

+0

Mi dispiace, ho perso la parte in cui dice esplicitamente: CreatePROPERTY ... il mio male. Avrà bisogno di ulteriori informazioni per i membri normali? Qualche soluzione? – Kjellski

+13

così triste che la proprietà ['' JsonSerializerSettings.MaxDepth'] (http://james.newtonking.com/projects/json/help/html/P_Newtonsoft_Json_JsonSerializerSettings_MaxDepth.htm) non si cura di questo – drzaus

1

È possibile utilizzare la riflessione per ispezionare l'oggetto e creare una copia che modifichi ciascun valore di proprietà secondo necessità. Per coincidenza ho appena reso pubblica una nuova libreria che rende questo genere di cose davvero facile. È possibile ottenere qui: https://github.com/jamietre/IQObjectMapper

Ecco un esempio del codice si usa

var newInstance = ObjectMapper.Map(obj,(value,del) => { 
    return value !=null && value.GetType().IsClass ? 
     null : 
     value; 
    }); 

la "mappa" metodo itera attraverso ogni proprietà dell'oggetto, e chiede un Func<object,IDelegateInfo> per ogni (IDelegateInfo avendo riflessione informazioni come il nome della proprietà, il tipo, ecc.). La funzione restituisce il nuovo valore per ogni proprietà. Quindi, in questo esempio, ho appena testato il valore di ogni proprietà per vedere se si tratta di una classe, e in tal caso, restituire null; in caso contrario, restituire il valore originale.

altro modo più espressivo per farlo:

var obj = new MyObject(); 

// map the object to a new dictionary   

var dict = ObjectMapper.ToDictionary(obj); 

// iterate through each item in the dictionary, a key/value pair 
// representing each property 

foreach (KeyValuePair<string,object> kvp in dict) { 
    if (kvp.Value!=null && kvp.Value.GetType().IsClass) { 
     dict[kvp.Key]=null; 
    } 
} 

// map back to an instance 

var newObject = ObjectMapper.ToNew<MyObject>(dict); 

In entrambi i casi, il valore di newInstance.myChildren (e le altre proprietà che sono non a valore tipizzato) sarà nullo. Potresti facilmente cambiare le regole per ciò che accade in questa mappatura.

Spero che questo aiuti. A proposito, dal tuo commento sembra che JSON non sia davvero il tuo obiettivo ma solo qualcosa che hai pensato ti avrebbe aiutato a raggiungerlo. Se vuoi finire con json, serializza semplicemente l'output di questo, ad es.

string json = JavaScriptSerializer.Serialize(newObject); 

Ma non vorrei coinvolgere Json se quello è stato solo un mezzo per un fine; se si desidera rimanere negli oggetti CLR, non è necessario utilizzare JSON come intermediario.

0

In primo luogo, volevo dire che tutto il merito dovrebbe andare a Nathan Baulch. Questo è un adattamento della sua risposta combinato con l'utilizzo di MaxDepth nelle impostazioni. Grazie per il tuo aiuto Nathan!

using Newtonsoft.Json; 
using Newtonsoft.Json.Serialization; 
using System; 
using System.Collections.Generic; 
using System.IO; 
using System.Linq; 
using System.Reflection; 
using System.Web; 
using System.Web.Mvc; 

namespace Helpers 
{ 
    public class JsonNetResult : JsonResult 
    { 
     public JsonNetResult() 
     { 
      Settings = new JsonSerializerSettings 
      { 
       ReferenceLoopHandling = ReferenceLoopHandling.Error 
      }; 
     } 

     public JsonSerializerSettings Settings { get; private set; } 

     public override void ExecuteResult(ControllerContext context) 
     { 
      if (context == null) 
       throw new ArgumentNullException("context"); 
      if (this.JsonRequestBehavior == JsonRequestBehavior.DenyGet && string.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)) 
       throw new InvalidOperationException("JSON GET is not allowed"); 

      HttpResponseBase response = context.HttpContext.Response; 
      response.ContentType = string.IsNullOrEmpty(this.ContentType) ? "application/json" : this.ContentType; 

      if (this.ContentEncoding != null) 
       response.ContentEncoding = this.ContentEncoding; 
      if (this.Data == null) 
       return; 

      var scriptSerializer = JsonSerializer.Create(this.Settings); 

      using (var sw = new StringWriter()) 
      { 
       if (Settings.MaxDepth != null) 
       { 
        using (var jsonWriter = new JsonNetTextWriter(sw)) 
        { 
         Func<bool> include =() => jsonWriter.CurrentDepth <= Settings.MaxDepth; 
         var resolver = new JsonNetContractResolver(include); 
         this.Settings.ContractResolver = resolver; 
         var serializer = JsonSerializer.Create(this.Settings); 
         serializer.Serialize(jsonWriter, Data); 
        } 
        response.Write(sw.ToString()); 
       } 
       else 
       { 
        scriptSerializer.Serialize(sw, this.Data); 
        response.Write(sw.ToString()); 
       } 
      } 
     } 
    } 

    public class JsonNetTextWriter : JsonTextWriter 
    { 
     public JsonNetTextWriter(TextWriter textWriter) : base(textWriter) { } 

     public int CurrentDepth { get; private set; } 

     public override void WriteStartObject() 
     { 
      CurrentDepth++; 
      base.WriteStartObject(); 
     } 

     public override void WriteEndObject() 
     { 
      CurrentDepth--; 
      base.WriteEndObject(); 
     } 
    } 

    public class JsonNetContractResolver : DefaultContractResolver 
    { 
     private readonly Func<bool> _includeProperty; 

     public JsonNetContractResolver(Func<bool> includeProperty) 
     { 
      _includeProperty = includeProperty; 
     } 

     protected override JsonProperty CreateProperty(
      MemberInfo member, MemberSerialization memberSerialization) 
     { 
      var property = base.CreateProperty(member, memberSerialization); 
      var shouldSerialize = property.ShouldSerialize; 
      property.ShouldSerialize = obj => _includeProperty() && 
               (shouldSerialize == null || 
               shouldSerialize(obj)); 
      return property; 
     } 
    } 
} 

Usa:

// instantiating JsonNetResult to handle circular reference issue. 
var result = new JsonNetResult 
{ 
    Data = <<The results to be returned>>, 
    JsonRequestBehavior = JsonRequestBehavior.AllowGet, 
    Settings = 
     { 
      ReferenceLoopHandling = ReferenceLoopHandling.Ignore, 
      MaxDepth = 1 
     } 
}; 

return result; 
0

Se si desidera utilizzare questo in un progetto ASP.NET core, forse non puoi implementare il proprio JsonTextWriter. Ma si può abitudine il DefaultContractResolver e IValueProvider

using Newtonsoft.Json; 
using Newtonsoft.Json.Serialization; 
using System; 
using System.Collections.Generic; 
using System.Reflection; 
using System.Linq; 

namespace customserialization 
{ 
    /// <summary> 
    /// IValueProvider personalizado para manejar max depth level 
    /// </summary> 
    public class CustomDynamicValueProvider : DynamicValueProvider, IValueProvider 
    { 
     MemberInfo _memberInfo; 
     MaxDepthHandler _levelHandler; 

     public CustomDynamicValueProvider(MemberInfo memberInfo, MaxDepthHandler levelHandler) : base(memberInfo) 
     { 
      _memberInfo = memberInfo; 
      _levelHandler = levelHandler; 
     } 

     public new object GetValue(object target) 
     { 
      //Si el valor a serializar es un objeto se incrementa el nivel de profundidad. En el caso de las listas el nivel se incrementa en el evento OnSerializing 
      if (((PropertyInfo)_memberInfo).PropertyType.IsClass) this._levelHandler.IncrementLevel(); 

      var rv = base.GetValue(target); 

      //Al finalizar la obtención del valor se decrementa. En el caso de las listas el nivel se decrementa en el evento OnSerialized 
      if (((PropertyInfo)_memberInfo).PropertyType.IsClass) this._levelHandler.DecrementLevel(); 

      return rv; 
     } 
    } 

    /// <summary> 
    /// Maneja los niveles de serialización 
    /// </summary> 
    public class MaxDepthHandler 
    { 
     int _maxDepth; 
     int _currentDepthLevel; 

     /// <summary> 
     /// Nivel actual 
     /// </summary> 
     public int CurrentDepthLevel { get { return _currentDepthLevel; } } 

     public MaxDepthHandler(int maxDepth) 
     { 
      this._currentDepthLevel = 1; 
      this._maxDepth = maxDepth; 
     } 

     /// <summary> 
     /// Incrementa el nivel actual 
     /// </summary> 
     public void IncrementLevel() 
     { 
      this._currentDepthLevel++; 
     } 

     /// <summary> 
     /// Decrementa el nivel actual 
     /// </summary> 
     public void DecrementLevel() 
     { 
      this._currentDepthLevel--; 
     } 

     /// <summary> 
     /// Determina si se alcanzó el nivel actual 
     /// </summary> 
     /// <returns></returns> 
     public bool IsMaxDepthLevel() 
     { 
      return !(this._currentDepthLevel < this._maxDepth); 
     } 
    } 

    public class ShouldSerializeContractResolver : DefaultContractResolver 
    { 

     MaxDepthHandler _levelHandler; 

     public ShouldSerializeContractResolver(int maxDepth) 
     { 
      this._levelHandler = new MaxDepthHandler(maxDepth); 
     } 


     void OnSerializing(object o, System.Runtime.Serialization.StreamingContext context) 
     { 
      //Antes de serializar una lista se incrementa el nivel. En el caso de los objetos el nivel se incrementa en el método GetValue del IValueProvider 
      if (o.GetType().IsGenericList()) 
       _levelHandler.IncrementLevel(); 
     } 

     void OnSerialized(object o, System.Runtime.Serialization.StreamingContext context) 
     { 
      //Despues de serializar una lista se decrementa el nivel. En el caso de los objetos el nivel se decrementa en el método GetValue del IValueProvider 
      if (o.GetType().IsGenericList()) 
       _levelHandler.DecrementLevel(); 
     } 

     protected override JsonContract CreateContract(Type objectType) 
     { 
      var contract = base.CreateContract(objectType); 
      contract.OnSerializingCallbacks.Add(new SerializationCallback(OnSerializing)); 
      contract.OnSerializedCallbacks.Add(new SerializationCallback(OnSerialized)); 

      return contract; 
     } 


     protected override IValueProvider CreateMemberValueProvider(MemberInfo member) 
     { 
      var rv = base.CreateMemberValueProvider(member); 

      if (rv is DynamicValueProvider) //DynamicValueProvider es el valueProvider usado en general 
      { 
       //Utilizo mi propio ValueProvider, que utilizar el levelHandler 
       rv = new CustomDynamicValueProvider(member, this._levelHandler); 
      } 

      return rv; 
     } 

     protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) 
     { 
      JsonProperty property = base.CreateProperty(member, memberSerialization); 

      var isObjectOrList = ((PropertyInfo)member).PropertyType.IsGenericList() || ((PropertyInfo)member).PropertyType.IsClass; 



      property.ShouldSerialize = 
        instance => 
        { 
         var shouldSerialize = true; 
         //Si se alcanzo el nivel maximo y la propiedad (member) actual a serializar es un objeto o lista no se serializa (shouldSerialize = false) 
         if (_levelHandler.IsMaxDepthLevel() && isObjectOrList) 
          shouldSerialize = false;       

         return shouldSerialize; 
        }; 

      return property; 
     } 



    } 

    public static class Util 
    { 
     public static bool IsGenericList(this Type type) 
     { 
      foreach (Type @interface in type.GetInterfaces()) 
      { 
       if (@interface.IsGenericType) 
       { 
        if (@interface.GetGenericTypeDefinition() == typeof(ICollection<>)) 
        { 
         // if needed, you can also return the type used as generic argument 
         return true; 
        } 
       } 
      } 
      return false; 
     } 
    } 
} 

e utilizzare questo nel vostro controller

 [HttpGet] 
     public IActionResult TestJSON() 
     { 
      var obj = new Thing 
      { 
       id = 1, 
       reference = new Thing 
       { 
        id = 2, 
        reference = new Thing 
        { 
         id = 3, 
         reference = new Thing 
         { 
          id = 4 
         } 
        } 
       } 
      }; 
      var settings = new JsonSerializerSettings() 
      { 
       ContractResolver = new ShouldSerializeContractResolver(2), 
      }; 

      return new JsonResult(obj, settings); 

     } 
Problemi correlati