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 del tipo 'ISomething'. ISomething puede ser una interfaz o una clase abstracta".
¿Existe un atributo o convertidor de Json.NET que me permita especificar una Something
clase concreta para usar durante la deserialización?
.net
serialization
json.net
dthrasher
fuente
fuente
Respuestas:
Una de las cosas que puede hacer con Json.NET es:
var settings = new JsonSerializerSettings(); settings.TypeNameHandling = TypeNameHandling.Objects; JsonConvert.SerializeObject(entity, Formatting.Indented, settings);
La
TypeNameHandling
bandera agregará una$type
propiedad al JSON, lo que le permite a Json.NET saber en qué tipo concreto necesita deserializar el objeto. Esto le permite deserializar un objeto sin dejar de cumplir con una interfaz o clase base abstracta.Sin embargo, la desventaja es que esto es muy específico de Json.NET. La
$type
habrá un tipo totalmente cualificado, por lo que si la serialización con información de tipo ,, las necesidades deserializador para poder entenderlo así.Documentación: Configuración de serialización con Json.NET
fuente
TypeNameHandling
. Consulte la precaución TypeNameHandling en Newtonsoft Json para obtener más detalles.Puede lograr esto mediante el uso de la clase JsonConverter. Suponga 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<Tycoon>(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.
fuente
En lugar de pasar un objeto JsonSerializerSettings personalizado 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 infle con 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; } }
fuente
En la versión más reciente del convertidor Newtonsoft Json de terceros, puede configurar 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; } }
Siempre que Something implemente ISomething, 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 contiene el tipo concreto.
PD. esto también le permite hacer que sus setters sean privados.
fuente
Tuve el mismo problema, así que se me ocurrió mi propio convertidor que usa argumentos de tipos conocidos.
public class JsonKnownTypeConverter : JsonConverter { public IEnumerable<Type> KnownTypes { get; set; } public JsonKnownTypeConverter(IEnumerable<Type> 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<T>(this string jsonString, IEnumerable<Type> knownTypes = null) { if (string.IsNullOrEmpty(jsonString)) return default(T); return JsonConvert.DeserializeObject<T>(jsonString, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto, Converters = new List<JsonConverter> ( 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 la clase.
fuente
Normalmente, siempre he usado la solución con la
TypeNameHandling
sugerencia de DanielT, pero en los casos aquí no he tenido control sobre el JSON entrante (y, por lo tanto, no puedo asegurarme de que incluya una$type
propiedad) .He escrito un convertidor personalizado que solo le permite especificar explícitamente el tipo de hormigón:public class Model { [JsonConverter(typeof(ConcreteTypeConverter<Something>))] public ISomething TheThing { get; set; } }
Esto solo usa la implementación del serializador predeterminada de Json.Net mientras especifica explícitamente el tipo concreto.
El código fuente y una descripción general están disponibles en esta publicación de blog .
fuente
Solo quería completar el ejemplo que @Daniel T. nos mostró arriba:
Si está utilizando 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<EntityType>(json, settings);
Así es como se conforma un json cuando se usa la
TypeNameHandling
bandera:fuente
Me he preguntado lo mismo, pero me temo que no se puede hacer.
Veámoslo de esta manera. Le entrega a JSon.net una cadena de datos y un tipo para deserializar. ¿Qué debe hacer JSON.net cuando golpea ese ISomething? 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 idea de cuál de los muchos objetos que pueden heredar ISomething debería usar. Las interfaces son algo que se puede serializar automáticamente, pero no deserializar automáticamente.
Lo que haría sería buscar reemplazar ISomething con una clase base. Con eso, es posible que pueda obtener el efecto que está buscando.
fuente
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 deba especificar una clase concreta que implemente la interfaz para la serialización JSON.
fuente