c# .net json serialization json.net

c# - ¿Cómo hago que JSON.NET ignore las relaciones de objeto?



serialization (3)

Además, si está buscando una forma de hacerlo para todas sus clases modelo con diferentes nombres de tipos de miembros ( por ejemplo, tiene algunos modelos creados por Entity Framework ), esta respuesta puede ayudar y puede ignorar las propiedades de navegación en la serialización JSON mediante eso.

Estoy trabajando en un proyecto de Entity Framework . Quiero serializar un grupo de instancias de clase de entidad. Los he agrupado en una clase contenedor:

public class Pseudocontext { public List<Widget> widgets; public List<Thing> things;

Etcétera ... es una instancia de esta clase a la que intento serializar. Quiero que JSON.NET serialice los miembros de cada instancia de clase de entidad que en realidad son columnas en la base de datos subyacente. No quiero que intente serializar referencias de objetos.

En particular, mis clases de entidad tienen miembros virtuales que me permiten escribir el código C # que navega todas mis relaciones entre entidades sin preocuparse por los valores reales de las claves, uniones, etc., y quiero que JSON.NET ignore las partes asociadas de mi entidad clases

En la superficie, parece haber una opción de configuración JSON.NET que hace exactamente lo que estoy diciendo:

JsonSerializer serializer = new JsonSerializer(); serializer.PreserveReferencesHandling = PreserveReferencesHandling.None;

Desafortunadamente, JSON.NET parece estar ignorando la segunda declaración anterior.

De hecho, encontré una página web ( http://json.codeplex.com/workitem/24608 ) donde alguien más llamó la atención del propio James Newton-King, y su respuesta (en su totalidad) fue "Escribe un solucionador de contrato personalizado ".

Por inadecuada que encuentre esa respuesta, he estado tratando de seguir su guía. Me gustaría poder escribir un "resolvedor de contratos" que ignore todo, excepto los tipos primitivos, cadenas, objetos DateTime y mi propia clase Pseudocontext junto con las listas que contiene directamente. Si alguien tiene un ejemplo de algo que al menos se parece a eso, podría ser todo lo que necesito. Esto es lo que se me ocurrió:

public class WhatDecadeIsItAgain : DefaultContractResolver { protected override JsonContract CreateContract(Type objectType) { JsonContract contract = base.CreateContract(objectType); if (objectType.IsPrimitive || objectType == typeof(DateTime) || objectType == typeof(string) || objectType == typeof(Pseudocontext) || objectType.Name.Contains("List")) { contract.Converter = base.CreateContract(objectType).Converter; } else { contract.Converter = myDefaultConverter; } return contract; } private static GeeThisSureTakesALotOfClassesConverter myDefaultConverter = new GeeThisSureTakesALotOfClassesConverter(); } public class GeeThisSureTakesALotOfClassesConverter : Newtonsoft.Json.Converters.CustomCreationConverter<object> { public override object Create(Type objectType) { return null; } }

Cuando intento utilizar lo anterior (estableciendo el serializador.ContractResolver en una instancia de WhatDecadeIsItAgain antes de la serialización), recibo errores de OutOfMemory durante la serialización que indican que JSON.NET encuentra bucles de referencia que nunca terminan (a pesar de mis esfuerzos para hacer JSON.NET solo ignora las referencias de objeto ).

Siento que mi "solucionador de contrato personalizado" puede estar equivocado. Como se muestra arriba, se basa en la premisa de que debo devolver el "contrato" predeterminado para los tipos que deseo serializar, y un "contrato" que simplemente devuelve "nulo" para todos los demás tipos.

No tengo idea de cuán correctas son estas suposiciones, sin embargo, y no es fácil de decir. El diseño de JSON.NET se basa en gran medida en la herencia de implementación, anulación de método, etc .; No soy muy OOP , y creo que ese tipo de diseño es bastante oscuro. Si pudiéramos implementar una interfaz de "resolución personalizada de contratos", Visual Studio 2012 podría anular rápidamente los métodos requeridos, y me imagino que tendría poco problema en llenar los resguardos con lógica real.

No tendría problemas para escribir, por ejemplo, un método que devuelva "verdadero" si quiero serializar un objeto del tipo suministrado y "falso" en caso contrario. Tal vez me esté perdiendo algo, pero no he encontrado ningún método para anularlo, ni he podido encontrar la interfaz hipotética (ICustomContractResolver?) Que me diga lo que se supone que debo hacer en el último fragmento de código. insertado arriba.

Además, me doy cuenta de que hay atributos JSON.NET ([JsonIgnore]?) Que están diseñados para tratar situaciones como esta. Realmente no puedo usar ese enfoque, ya que estoy usando "modelo primero". A menos que decida destruir toda la arquitectura de mi proyecto, mis clases de entidades se generarán automáticamente, y no contendrán los atributos JsonIgnore, ni me siento cómodo editando las clases automatizadas para contener estos atributos.

A propósito, por un tiempo tuve cosas configuradas para serializar referencias de objetos, y estaba ignorando todos los datos superfluos "$ ref" y "$ id" que JSON.NET estaba devolviendo en su salida de serialización. Por el momento, abandoné ese enfoque, porque (de pronto, la serialización comenzó a tomar una cantidad de tiempo desmesurada (~ 45 minutos para obtener ~ 5 MB de JSON).

No he podido relacionar ese cambio repentino en el rendimiento con algo específico que hice. En todo caso, el volumen de datos en mi base de datos es más bajo ahora de lo que era cuando la serialización realmente se completaba en un tiempo razonable. Pero estaría más que feliz con un retorno al status quo ante (en el que simplemente tenía que ignorar "$ ref", "$ id", etc.) si eso se pudiera lograr.

En este punto, también estoy abierto a la posibilidad de utilizar alguna otra biblioteca JSON o una estrategia diferente. Siento que podría usar StringBuilder, System.Reflection, etc. y llegar con mi propia solución casera ... pero ¿no se supone que JSON.NET es capaz de manejar este tipo de cosas con bastante facilidad?


En primer lugar, para solucionar sus problemas con los bucles de referencia: la configuración PreserveReferencesHandling controla si Json.Net emite $id y $ref para rastrear las referencias entre objetos. Si tiene esto configurado en None y su gráfico de objetos contiene bucles, entonces también necesitará establecer ReferenceLoopHandling para Ignore para evitar errores.

Ahora, para hacer que Json.Net ignore por completo todas las referencias de objeto y solo serialice las propiedades primitivas (excepto en su clase de curso de Pseudocontext ), necesita un Resolver de Contratos personalizado, como sugirió. Pero no te preocupes, no es tan difícil como crees. El resolver tiene la capacidad de inyectar un método ShouldSerialize para cada propiedad para controlar si esa propiedad se debe incluir o no en el resultado. Por lo tanto, todo lo que necesita hacer es derivar su resolución del predeterminado, y luego anular el método CreateProperty para que establezca ShouldSerialize adecuada. (No necesita un JsonConverter personalizado aquí, aunque es posible resolver este problema con ese enfoque, aunque con bastante más código).

Aquí está el código para el resolver:

class CustomResolver : DefaultContractResolver { protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { JsonProperty prop = base.CreateProperty(member, memberSerialization); if (prop.DeclaringType != typeof(PseudoContext) && prop.PropertyType.IsClass && prop.PropertyType != typeof(string)) { prop.ShouldSerialize = obj => false; } return prop; } }

Aquí hay una demostración completa que muestra el resolver en acción.

class Program { static void Main(string[] args) { // Set up some dummy data complete with reference loops Thing t1 = new Thing { Id = 1, Name = "Flim" }; Thing t2 = new Thing { Id = 2, Name = "Flam" }; Widget w1 = new Widget { Id = 5, Name = "Hammer", IsActive = true, Price = 13.99M, Created = new DateTime(2013, 12, 29, 8, 16, 3), Color = Color.Red, }; w1.RelatedThings = new List<Thing> { t2 }; t2.RelatedWidgets = new List<Widget> { w1 }; Widget w2 = new Widget { Id = 6, Name = "Drill", IsActive = true, Price = 45.89M, Created = new DateTime(2014, 1, 22, 2, 29, 35), Color = Color.Blue, }; w2.RelatedThings = new List<Thing> { t1 }; t1.RelatedWidgets = new List<Widget> { w2 }; // Here is the container class we wish to serialize PseudoContext pc = new PseudoContext { Things = new List<Thing> { t1, t2 }, Widgets = new List<Widget> { w1, w2 } }; // Serializer settings JsonSerializerSettings settings = new JsonSerializerSettings(); settings.ContractResolver = new CustomResolver(); settings.PreserveReferencesHandling = PreserveReferencesHandling.None; settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; settings.Formatting = Formatting.Indented; // Do the serialization and output to the console string json = JsonConvert.SerializeObject(pc, settings); Console.WriteLine(json); } class PseudoContext { public List<Thing> Things { get; set; } public List<Widget> Widgets { get; set; } } class Thing { public int Id { get; set; } public string Name { get; set; } public List<Widget> RelatedWidgets { get; set; } } class Widget { public int Id { get; set; } public string Name { get; set; } public bool IsActive { get; set; } public decimal Price { get; set; } public DateTime Created { get; set; } public Color Color { get; set; } public List<Thing> RelatedThings { get; set; } } enum Color { Red, White, Blue } }

Salida:

{ "Things": [ { "Id": 1, "Name": "Flim" }, { "Id": 2, "Name": "Flam" } ], "Widgets": [ { "Id": 5, "Name": "Hammer", "IsActive": true, "Price": 13.99, "Created": "2013-12-29T08:16:03", "Color": 0 }, { "Id": 6, "Name": "Drill", "IsActive": true, "Price": 45.89, "Created": "2014-01-22T02:29:35", "Color": 2 } ] }

Espero que esto esté en el estadio de lo que estabas buscando.


Un método más fácil es modificar la plantilla de su modelo T4 (.tt) para agregar atributos JSONIgnore a las propiedades de navegación, lo que simplemente dejará los tipos primitivos como serializables.