2012-05-04 30 views
11

Solo quiero el primer nivel de profundidad de un objeto (no quiero hijos). Estoy dispuesto a usar cualquier biblioteca disponible. La mayoría de las bibliotecas simplemente lanzarán una excepción cuando se alcance la profundidad de recursión, en lugar de ignorarla. Si esto no es posible, ¿hay alguna manera de ignorar la serialización de ciertos miembros dado un cierto tipo de datos?¿Cómo serializar o deserializar un Objeto JSON a una cierta profundidad en C#?

Editar: Digamos que tengo un objeto de esta manera:

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

Quiero eliminar cualquier niño (tipos complejos, incluso) para devolver un objeto como éste:

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

Se puede mostrar algún ejemplo de un problema JSON? – igofed

+1

¿Puedes simplemente crear un nuevo objeto sin hijos y serializar eso? –

+0

Lógicamente eso tendría sentido, pero quiero quitar cualquier objeto de tipo Objeto de sus hijos. Pensé que la serialización json sería la mejor manera de hacerlo, pero definitivamente estoy abierto a sugerencias. –

Respuesta

24

Este es posible en Json.NET usando alguna coordinación entre JsonWriter y el serializador ContractResolver.

Un JsonWriter personalizado incrementa un contador cuando se inicia un objeto y luego lo vuelve a disminuir cuando termina.

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

Una costumbre ContractResolver aplica un predicado ShouldSerialize especial en todas las propiedades que se utilizarán para verificar la profundidad actual.

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

El siguiente método muestra cómo estas dos clases personalizadas funcionan juntas.

public static string SerializeObject(object obj, int maxDepth) 
{ 
    using (var strWriter = new StringWriter()) 
    { 
     using (var jsonWriter = new CustomJsonTextWriter(strWriter)) 
     { 
      Func<bool> include =() => jsonWriter.CurrentDepth <= maxDepth; 
      var resolver = new CustomContractResolver(include); 
      var serializer = new JsonSerializer {ContractResolver = resolver}; 
      serializer.Serialize(jsonWriter, obj); 
     } 
     return strWriter.ToString(); 
    } 
} 

El siguiente código de prueba demuestra la limitación de la profundidad máxima a 1 y 2 niveles, respectivamente.

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

No puedo hacer que esto funcione en la versión actual de la biblioteca Json.Net. Parece que los métodos CustomContractResolvers nunca se llaman. – Kjellski

+0

Lo siento, me he perdido la parte donde dice explícitamente: CreatePROPERTY ... my bad. ¿Necesitará más información para los miembros normales? Alguna solución? – Kjellski

+13

tan triste que la propiedad ['JsonSerializerSettings.MaxDepth'] (http://james.newtonking.com/projects/json/help/html/P_Newtonsoft_Json_JsonSerializerSettings_MaxDepth.htm) no se ocupa de esto – drzaus

1

Puede usar el reflejo para inspeccionar el objeto y hacer una copia que cambie el valor de cada propiedad según sea necesario. Casualmente, acabo de hacer pública una nueva biblioteca que hace que este tipo de cosas sea realmente fácil. Se puede conseguir aquí: https://github.com/jamietre/IQObjectMapper

He aquí un ejemplo del código que utilizaría

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

el "mapa" en iteración del método a través de cada propiedad del objeto, y pide una Func<object,IDelegateInfo> para cada uno (IDelegateInfo con la reflexión información como el nombre de la propiedad, tipo, etc.). La función devuelve el nuevo valor para cada propiedad. Entonces en este ejemplo, simplemente pruebo el valor de cada propiedad para ver si es una clase, y si es así, devuelvo nulo; si no, devuelve el valor original.

Otra forma más expresiva de hacerlo:

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

En cualquier caso, el valor de newInstance.myChildren (y cualquier otra propiedad que se escriben sin valor) será nulo. Podría cambiar fácilmente las reglas de lo que sucede en este mapeo.

Espero que esto ayude. Por cierto, a partir de tu comentario, parece que JSON no es realmente tu objetivo, sino solo algo que pensaste que te ayudaría a lograrlo. Si quieres terminar con json, solo serializa el resultado de esto, p.

string json = JavaScriptSerializer.Serialize(newObject); 

Pero yo no involucraría a json si eso fuera solo un medio para un fin; si desea permanecer en objetos CLR, entonces no es necesario usar JSON como intermediario.

0

Primero, quería decir que todo el crédito debe ir a Nathan Baulch. Esta es una adaptación de su respuesta combinada con el uso de MaxDepth en la configuración. Gracias por tu ayuda 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; 
     } 
    } 
} 

Uso:

// 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

Si desea utilizar esto en un proyecto ASP.NET Core, tal vez usted no puede implementar su propio JsonTextWriter. Pero se puede encargo del DefaultContractResolver y 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; 
     } 
    } 
} 

y utilizar esto en su controlador

 [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); 

     } 
Cuestiones relacionadas