Usar convertidores Json.NET para deserializar propiedades

Tengo una definición de clase que contiene una propiedad que devuelve una interfaz.

public class Foo { public int Number { get; set; } public ISomething Thing { get; set; } } 

Intentar serializar la clase Foo usando Json.NET me da un mensaje de error como “No se pudo crear una instancia de tipo ‘ISomething’. ISomething puede ser una interfaz o clase abstracta.”

¿Hay algún atributo o convertidor Json.NET que me permita especificar una clase concreta de Something para utilizar durante la deserialización?

Una de las cosas que puedes hacer con Json.NET es:

 var settings = new JsonSerializerSettings(); settings.TypeNameHandling = TypeNameHandling.Objects; JsonConvert.SerializeObject(entity, Formatting.Indented, settings); 

La bandera TypeNameHandling agregará una propiedad $type a JSON, que permite a Json.NET saber en qué tipo de concreto necesita deserializar el objeto. Esto le permite deserializar un objeto sin dejar de cumplir una interfaz o clase base abstracta.

La desventaja, sin embargo, es que esto es muy específico de Json.NET. El $type será un tipo totalmente calificado, por lo que si lo serializa con tipo de información, el deserializador debe ser capaz de comprenderlo también.

Documentación: configuración de serialización con Json.NET

Puede lograr esto mediante el uso de la clase JsonConverter. Supongamos que tiene una clase con una propiedad de interfaz;

 public class Organisation { public string Name { get; set; } [JsonConverter(typeof(TycoonConverter))] public IPerson Owner { get; set; } } public interface IPerson { string Name { get; set; } } public class Tycoon : IPerson { public string Name { get; set; } } 

Su JsonConverter es responsable de serializar y deserializar la propiedad subyacente;

 public class TycoonConverter : JsonConverter { public override bool CanConvert(Type objectType) { return (objectType == typeof(IPerson)); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { return serializer.Deserialize(reader); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { // Left as an exercise to the reader :) throw new NotImplementedException(); } } 

Cuando trabaja con una organización deserializada a través de Json.Net, la IPerson subyacente para la propiedad Owner será de tipo Tycoon.

En lugar de pasar un objeto personalizado JsonSerializerSettings a JsonConvert.SerializeObject () con la opción TypeNameHandling.Objects, como se mencionó anteriormente, puede marcar esa propiedad de interfaz específica con un atributo para que el JSON generado no se hinche con las propiedades “$ type” en CADA objeto:

 public class Foo { public int Number { get; set; } // Add "$type" property containing type info of concrete class. [JsonProperty( TypeNameHandling = TypeNameHandling.Objects )] public ISomething { get; set; } } 

En la versión más reciente del convertidor Newtonsoft Json de terceros puede establecer un constructor con un tipo concreto relacionado con la propiedad interconectada.

 public class Foo { public int Number { get; private set; } public ISomething IsSomething { get; private set; } public Foo(int number, Something concreteType) { Number = number; IsSomething = concreteType; } } 

Mientras algo implemente algo, esto debería funcionar. Además, no coloque un constructor vacío predeterminado en caso de que el convertidor JSon intente usarlo, debe forzarlo a usar el constructor que contenga el tipo concreto.

PD. esto también le permite hacer que sus incubadores sean privados.

Tuve el mismo problema, así que se me ocurrió mi propio convertidor que usa tipos conocidos de argumentos.

 public class JsonKnownTypeConverter : JsonConverter { public IEnumerable KnownTypes { get; set; } public JsonKnownTypeConverter(IEnumerable knownTypes) { KnownTypes = knownTypes; } protected object Create(Type objectType, JObject jObject) { if (jObject["$type"] != null) { string typeName = jObject["$type"].ToString(); return Activator.CreateInstance(KnownTypes.First(x =>typeName.Contains("."+x.Name+","))); } throw new InvalidOperationException("No supported type"); } public override bool CanConvert(Type objectType) { if (KnownTypes == null) return false; return (objectType.IsInterface || objectType.IsAbstract) && KnownTypes.Any(objectType.IsAssignableFrom); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { // Load JObject from stream JObject jObject = JObject.Load(reader); // Create target object based on JObject var target = Create(objectType, jObject); // Populate the object properties serializer.Populate(jObject.CreateReader(), target); return target; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } } 

Definí dos métodos de extensión para deserializar y serializar:

 public static class AltiJsonSerializer { public static T DeserializeJson(this string jsonString, IEnumerable knownTypes = null) { if (string.IsNullOrEmpty(jsonString)) return default(T); return JsonConvert.DeserializeObject(jsonString, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto, Converters = new List ( new JsonConverter[] { new JsonKnownTypeConverter(knownTypes) } ) } ); } public static string SerializeJson(this object objectToSerialize) { return JsonConvert.SerializeObject(objectToSerialize, Formatting.Indented, new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.Auto}); } } 

Puede definir su propia forma de comparar e identificar tipos en los convertidos, solo uso el nombre de clase.

Normalmente siempre he usado la solución con TypeNameHandling como sugiere DanielT, pero en los casos aquí no he tenido control sobre el JSON entrante (y por lo tanto no puedo asegurar que incluya una propiedad $type ) He escrito un convertidor personalizado que solo te permite para especificar explícitamente el tipo concreto:

 public class Model { [JsonConverter(typeof(ConcreteTypeConverter))] public ISomething TheThing { get; set; } } 

Esto solo usa la implementación del serializador predeterminado de Json.Net mientras se especifica explícitamente el tipo de concreto.

El código fuente y una descripción general están disponibles en esta publicación de blog .

Solo quería completar el ejemplo que @Daniel T. nos mostró arriba:

Si está usando este código para serializar su objeto:

 var settings = new JsonSerializerSettings(); settings.TypeNameHandling = TypeNameHandling.Objects; JsonConvert.SerializeObject(entity, Formatting.Indented, settings); 

El código para deserializar el json debería verse así:

 var settings = new JsonSerializerSettings(); settings.TypeNameHandling = TypeNameHandling.Objects; var entity = JsonConvert.DeserializeObject(json, settings); 

Así es como se conforma un json cuando usa el indicador TypeNameHandling : enter image description here

Me he preguntado lo mismo, pero me temo que no se puede hacer.

Veámoslo de esta manera. Le das a JSon.net una cadena de datos y un tipo para deserializar. ¿Qué debe hacer JSON.net cuando se trata de ISIShing? No puede crear un nuevo tipo de ISomething porque ISomething no es un objeto. Tampoco puede crear un objeto que implemente ISomething, ya que no tiene ni idea de cuál de los muchos objetos que pueden heredar ISomething debe usar. Las interfaces son algo que se puede serializar automáticamente, pero no deserializar automáticamente.

Lo que haría sería considerar reemplazar ISomething con una clase base. Usando eso, es posible que puedas obtener el efecto que estás buscando.

Aquí hay una referencia a un artículo escrito por ScottGu

Basado en eso, escribí un código que creo que podría ser útil

 public interface IEducationalInstitute { string Name { get; set; } } public class School : IEducationalInstitute { private string name; #region IEducationalInstitute Members public string Name { get { return name; } set { name = value; } } #endregion } public class Student { public IEducationalInstitute LocalSchool { get; set; } public int ID { get; set; } } public static class JSONHelper { public static string ToJSON(this object obj) { JavaScriptSerializer serializer = new JavaScriptSerializer(); return serializer.Serialize(obj); } public static string ToJSON(this object obj, int depth) { JavaScriptSerializer serializer = new JavaScriptSerializer(); serializer.RecursionLimit = depth; return serializer.Serialize(obj); } } 

Y así es como lo llamarías

 School myFavSchool = new School() { Name = "JFK High School" }; Student sam = new Student() { ID = 1, LocalSchool = myFavSchool }; string jSONstring = sam.ToJSON(); Console.WriteLine(jSONstring); //Result {"LocalSchool":{"Name":"JFK High School"},"ID":1} 

Si lo entiendo correctamente, no creo que necesite especificar una clase concreta que implemente la interfaz para la serialización JSON.