serializeobject serialize serializar objects newtonsoft net jsonconvert example deserializeobject deserialize deserializar c# asp.net-web-api json.net

c# - serialize - newtonsoft.json example



Loop de referencia en Json.Net JsonSerializer en JsonConverter personalizado(API web) (7)

El proyecto es un servicio web Asp.Net Web API.

Tengo una jerarquía de tipos a la que debo poder serializar y desde Json, así que tomé el código de este SO: ¿cómo implementar JsonConverter personalizado en JSON.NET para deserializar una lista de objetos de clase base? , y apliqué el convertidor a la clase base de mi jerarquía; algo así (hay pseudocódigo aquí para ocultar irrelevancias):

[JsonConverter(typeof(TheConverter))] public class BaseType { // note the base of this type here is from the linked SO above private class TheConverter : JsonCreationConverter<BaseType> { protected override BaseType Create(Type objectType, JObject jObject) { Type actualType = GetTypeFromjObject(jObject); /*method elided*/ return (BaseType)Activator.CreateInstance(actualType); } } } public class RootType { public BaseType BaseTypeMember { get; set; } } public class DerivedType : BaseType { }

Por lo tanto, si deserializo una instancia de RootType cuyo BaseTypeMember fue igual a una instancia de DerivedType , se deserializará nuevamente en una instancia de ese tipo.

Para el registro, estos objetos JSON contienen un campo ''$type'' que contiene nombres de tipos virtuales (nombres de tipos .Net no completos), por lo que puedo admitir tipos simultáneamente en el JSON mientras controlo exactamente qué tipos se pueden serializar y deserializar.

Ahora esto funciona muy bien para deserializar los valores de la solicitud; pero tengo un problema con la serialización. Si miras el SO vinculado, y de hecho el debate de Json.Net que está vinculado desde la respuesta superior, verás que el código base que estoy usando está completamente orientado a la deserialización; con ejemplos de su uso que muestran la creación manual del serializador. La implementación de JsonConverter llevada a la mesa por este JsonCreationConverter<T> simplemente arroja una NotImplementedException .

Ahora, debido a la forma en que la API web usa un único formateador para una solicitud, necesito implementar la serialización ''estándar'' en el método WriteObject .

Debo enfatizar en este punto que antes de embarcarme en esta parte de mi proyecto tenía todo serializado correctamente sin errores .

Entonces hice esto:

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

Pero obtengo una JsonSerializationException : Self referencing loop detected with type ''DerivedType'' , cuando uno de los objetos se serializa. Nuevamente, si elimino el atributo convertidor (deshabilitando mi creación personalizada), entonces funciona bien ...

Tengo la sensación de que esto significa que mi código de serialización está activando el convertidor de nuevo en el mismo objeto, que a su vez llama al serializador de nuevo - ad nauseam. Confirmado - ver mi respuesta

Entonces, ¿qué código debería estar escribiendo en WriteObject que haga la misma serialización ''estándar'' que funciona?


¡Me acabo de dar cuenta de esto y me estaba tirando de los pelos por la frustración!

Para resolver el problema, lo siguiente funcionó para mí, pero como me perdí la solución CanWrite , es una solución más compleja.

  • Crea una copia de la clase existente en la que estás usando tu convertidor y llámalo algo diferente.
  • Elimine el atributo JsonConverter en la copia.
  • Cree un constructor en la nueva clase que tome un parámetro del mismo tipo que la clase original. Use el constructor para copiar los valores que sean necesarios para la serialización posterior.
  • En el método WriteJson su convertidor, convierta el valor en su tipo ficticio, luego en su lugar serialice ese tipo.

Por ejemplo, esto es similar a mi clase original:

[JsonConverter(typeof(MyResponseConverter))] public class MyResponse { public ResponseBlog blog { get; set; } public Post[] posts { get; set; } }

La copia se ve así:

public class FakeMyResponse { public ResponseBlog blog { get; set; } public Post[] posts { get; set; } public FakeMyResponse(MyResponse response) { blog = response.blog; posts = response.posts; } }

El WriteJson es:

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { if (CanConvert(value.GetType())) { FakeMyResponse response = new FakeMyResponse((MyResponse)value); serializer.Serialize(writer, response); } }

Editar:

El OP señaló que usar una Expando podría ser otra posible solución. Esto funciona bien, ahorrándose la molestia de crear la nueva clase, aunque el soporte DLR requiere Framework 4.0 o posterior. El enfoque consiste en crear un nuevo ExpandoObject dynamic y luego inicializar sus propiedades en el método WriteJson directamente para crear la copia, por ejemplo:

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { if (CanConvert(value.GetType())) { var response = (MyResponse)value; dynamic fake = new System.Dynamic.ExpandoObject(); fake.blog = response.blog; fake.posts = response.posts; serializer.Serialize(writer, fake); } }


Acabo de tener el mismo problema con las colecciones de padres / hijos y encontré esa publicación que resolvió mi caso. Solo quería mostrar la Lista de elementos de colección para padres y no necesitaba ninguno de los datos secundarios, por lo tanto uso lo siguiente y funcionó bien:

JsonConvert.SerializeObject(ResultGroups, Formatting.None, new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore });

también se refiere a la página de Jolly.NET codplex en:

http://json.codeplex.com/discussions/272371


Bueno, esto fue divertido ...

Cuando miré más de cerca el rastro de la pila para la excepción, noté que el método JsonSerializerInternalWriter.SerializeConvertable estaba allí dos veces, de hecho era ese método uno en la parte superior de la pila - invocando a JsonSerializerInternalWriter.CheckForCircularReference - que a su vez arrojaba el excepción. También fue, sin embargo, la fuente de la llamada al método de Write mi propio convertidor.

Por lo que parece que el serializador estaba haciendo:

  • 1) Si el objeto tiene un convertidor
    • 1a) Lanzar si la referencia circular
    • 1b) Método de escritura del convertidor de invocación
  • 2) Else
    • 2a) Usa serializadores internos

Entonces, en este caso, Json.Net está llamando a mi convertidor, que a su vez está llamando al serializador Json.Net, que luego explota porque ve que ya está serializando el objeto que se le pasó.

Apertura de ILSpy en el archivo DLL (sí, sé que es de código abierto, pero quiero la funcionalidad de llamadas) y moviendo hacia arriba la pila de llamadas de SerializeConvertable a JsonSerializerInternalWriter.SerializeValue , el código que detecta si se debe usar un convertidor se puede encontrar bien cerca del comienzo:

if (((jsonConverter = ((member != null) ? member.Converter : null)) != null || (jsonConverter = ((containerProperty != null) ? containerProperty.ItemConverter : null)) != null || (jsonConverter = ((containerContract != null) ? containerContract.ItemConverter : null)) != null || (jsonConverter = valueContract.Converter) != null || (jsonConverter = this.Serializer.GetMatchingConverter(valueContract.UnderlyingType)) != null || (jsonConverter = valueContract.InternalConverter) != null) && jsonConverter.CanWrite) { this.SerializeConvertable(writer, jsonConverter, value, valueContract, containerContract, containerProperty); return; }

Afortunadamente, la última condición en la sentencia if proporciona la solución a mi problema: todo lo que tenía que hacer era agregar lo siguiente al convertidor base copiado del código en el SO vinculado en la pregunta, o en el derivado:

public override bool CanWrite { get { return false; } }

Y ahora todo funciona bien.

El resultado de esto, sin embargo, es que si tiene la intención de tener una serialización JSON personalizada en un objeto y lo está inyectando con un convertidor y tiene la intención de recurrir al mecanismo de serialización estándar en algunas o todas las situaciones; entonces no puede porque engañará al marco y le hará pensar que está intentando almacenar una referencia circular.

Intenté manipular el miembro ReferenceLoopHandling , pero si le dije que los Ignore , entonces no se serializó nada y, como era de esperar, les dije que los guardaran, como era de esperar, tuve un desbordamiento de la pila.

Es posible que esto sea un error en Json.Net - está bien que sea un caso extremo que esté en peligro de caerse del borde del universo - pero si te encuentras en esta situación, entonces estás como atrapado !


El mío fue un error simple, y no tuvo nada que ver con la solución de este tema.

Este tema fue la primera página en google, así que estoy publicando aquí en caso de que otros tengan el mismo problema que yo.

dynamic table = new ExpandoObject(); .. .. table.rows = table; <<<<<<<< I assigned same dynamic object to itself.


Encontré este problema usando la versión 4.5.7.15008 de Newtonsoft.Json. Intenté todas las soluciones ofrecidas aquí junto con algunas otras. Resolví el problema usando el código a continuación. Básicamente, puede usar otro JsonSerializer para realizar la serialización. El JsonSerializer que se crea no tiene ningún convertidor registrado por lo que se evitará la reentrada / excepción. Si se utilizan otras configuraciones o ContractResolver, entonces deberán configurarse manualmente en la serialización que se crea: algunos argumentos del constructor podrían agregarse a la clase CustomConverter para acomodar esto.

public class CustomConverter : JsonConverter { /// <summary> /// Use a privately create serializer so we don''t re-enter into CanConvert and cause a Newtonsoft exception /// </summary> private readonly JsonSerializer noRegisteredConvertersSerializer = new JsonSerializer(); public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { bool meetsCondition = false; /* add condition here */ if (!meetsCondition) writer.WriteNull(); else noRegisteredConvertersSerializer.Serialize(writer, value); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { throw new NotImplementedException(); } public override bool CanConvert(Type objectType) { // example: register accepted conversion types here return typeof(IDictionary<string, object>).IsAssignableFrom(objectType); } }


Esto podría ayudar a alguien, pero en mi caso estaba intentando anular el método Equals para que mi objeto sea tratado como tipo de valor. En mi investigación, descubrí que a JSON.NET no le gusta esto:

Error de referencia de JSON.NET


OMI, esta es una seria limitación de la biblioteca. La solución es bastante simple, aunque admitiré que no me vino tan rápido. La solución es establecer:

.ReferenceLoopHandling = ReferenceLoopHandling.Serialize

que, como está documentado en todo el lugar, eliminará el error de autoreferencia y lo reemplazará con un desbordamiento de pila. En mi caso, requirí funcionalidad de escritura, por lo que configurar CanWrite en falso no era una opción. Al final, simplemente configuré una bandera para proteger la llamada de CanConvert cuando sé que una llamada al serializador va por una recurrencia (interminable):

Public Class ReferencingObjectConverter : Inherits JsonConverter Private _objects As New HashSet(Of String) Private _ignoreNext As Boolean = False Public Overrides Function CanConvert(objectType As Type) As Boolean If Not _ignoreNext Then Return GetType(IElement).IsAssignableFrom(objectType) AndAlso Not GetType(IdProperty).IsAssignableFrom(objectType) Else _ignoreNext = False Return False End If End Function Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer) Try If _objects.Contains(CType(value, IElement).Id.Value) Then ''insert a reference to existing serialized object serializer.Serialize(writer, New Reference With {.Reference = CType(value, IElement).Id.Value}) Else ''add to my list of processed objects _objects.Add(CType(value, IElement).Id.Value) ''the serialize will trigger a call to CanConvert (which is how we got here it the first place) ''and will bring us right back here with the same ''value'' parameter (and SO eventually), so flag ''the CanConvert function to skip the next call. _ignoreNext = True serializer.Serialize(writer, value) End If Catch ex As Exception Trace.WriteLine(ex.ToString) End Try End Sub Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object Throw New NotImplementedException() End Function Private Class Reference Public Property Reference As String End Class End Class