38 votos

Fundición de interfaces para la deserialización en JSON.NET

Estoy tratando de configurar un lector que se tome en objetos JSON desde diversos sitios web (pensar que la información raspado) y traducirlos en C# los objetos. Actualmente estoy usando JSON.NET para el proceso de deserialización. El problema que yo postulo es que no saben cómo manejar a nivel de interfaz propiedades de una clase. Por lo tanto, algo de la naturaleza:

public IThingy Thing

Se producirá el error:

No se pudo crear una instancia de tipo IThingy. El tipo es una interfaz o clase abstracta y no puede ser instanciado.

Es relativamente importante a tener que ser un IThingy frente a una Cosita ya que el código en el que estoy trabajando es considerada confidencial y de pruebas de unidad es muy importante. Las burlas de los objetos para atómica de secuencias de comandos de prueba no es posible, con pleno derecho a los objetos, como la Cosita. Deben ser de una interfaz.

He estado estudiando sobre JSON.NET's de la documentación por un tiempo ahora, y las preguntas que he podido encontrar en este sitio relacionadas con este, son todos de hace más de un año. Alguna ayuda?

También, si importa, mi aplicación está escrito en .NET 4.0.

31voto

Mark Meuer Puntos 1172

@SamualDavis proporciona una gran solución en una pregunta relacionada, que voy a resumir aquí.

Si usted tiene deserializar una secuencia de JSON en una clase concreta que tiene propiedades de la interfaz, puede incluir las clases concretas como parámetros a un constructor para la clase! El NewtonSoft deserializer es lo suficientemente inteligente como para saber que se necesita para el uso de esas clases concretas para deserializar las propiedades.

He aquí un ejemplo:

public class Visit : IVisit
{
    /// <summary>
    /// This constructor is required for the JSON deserializer to be able
    /// to identify concrete classes to use when deserializing the interface properties.
    /// </summary>
    public Visit(MyLocation location, Guest guest)
    {
        Location = location;
        Guest = guest;
    }
    public long VisitId { get; set; }
    public ILocation Location { get;  set; }
    public DateTime VisitDate { get; set; }
    public IGuest Guest { get; set; }
}

18voto

Eric Boumendil Puntos 436

Para habilitar la deserialización de múltiples implementaciones de las interfaces, se puede utilizar JsonConverter, pero no a través de un atributo:

Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer();
serializer.Converters.Add(new DTOJsonConverter());
Interfaces.IEntity entity = serializer.Deserialize(jsonReader);

DTOJsonConverter mapas de cada interfaz con una implementación concreta:

class DTOJsonConverter : Newtonsoft.Json.JsonConverter
{
    private static readonly string ISCALAR_FULLNAME = typeof(Interfaces.IScalar).FullName;
    private static readonly string IENTITY_FULLNAME = typeof(Interfaces.IEntity).FullName;


    public override bool CanConvert(Type objectType)
    {
        if (objectType.FullName == ISCALAR_FULLNAME
            || objectType.FullName == IENTITY_FULLNAME)
        {
            return true;
        }
        return false;
    }

    public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
    {
        if (objectType.FullName == ISCALAR_FULLNAME)
            return serializer.Deserialize(reader, typeof(DTO.ClientScalar));
        else if (objectType.FullName == IENTITY_FULLNAME)
            return serializer.Deserialize(reader, typeof(DTO.ClientEntity));

        throw new NotSupportedException(string.Format("Type {0} unexpected.", objectType));
    }

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

DTOJsonConverter sólo es necesaria para la deserializer. El proceso de serialización es invariable. El objeto Json no se deben integrar de hormigón tipos de nombres.

De esta MANERA posterior ofrece la misma solución un paso más allá con un genérico JsonConverter.

13voto

Steve Greatrex Puntos 8466

(Copiado de esta pregunta)

En los casos en los que no han tenido control sobre el entrante JSON (y por lo tanto no puede garantizar que se incluye un $tipo de propiedad) he escrito un convertidor personalizado que sólo le permite especificar de forma explícita el tipo concreto:

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

Esto sólo utiliza el valor predeterminado serializador de la aplicación de Json.Net mientras que especificar explícitamente el tipo concreto.

El código fuente y una visión general están disponibles en este blog.

3voto

mcw0933 Puntos 865

Dos cosas que usted puede intentar:

Implementar un try/analizar el modelo:

public class Organisation {
  public string Name { get; set; }

  [JsonConverter(typeof(RichDudeConverter))]
  public IPerson Owner { get; set; }
}

public interface IPerson {
  string Name { get; set; }
}

public class Tycoon : IPerson {
  public string Name { get; set; }
}

public class Magnate : IPerson {
  public string Name { get; set; }
  public string IndustryName { get; set; }
}

public class Heir: IPerson {
  public string Name { get; set; }
  public IPerson Benefactor { get; set; }
}

public class RichDudeConverter : JsonConverter
{
  public override bool CanConvert(Type objectType)
  {
    return (objectType == typeof(IPerson));
  }

  public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
  {
    // pseudo-code
    object richDude = serializer.Deserialize<Heir>(reader);

    if (richDude == null)
    {
        richDude = serializer.Deserialize<Magnate>(reader);
    }

    if (richDude == null)
    {
        richDude = serializer.Deserialize<Tycoon>(reader);
    }

    return richDude;
  }

  public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
  {
    // Left as an exercise to the reader :)
    throw new NotImplementedException();
  }
}

O, si usted puede hacerlo en su modelo de objetos, implementar una base de hormigón de clase entre IPerson y sus objetos de hoja, y deserializar.

La primera puede potencialmente error en tiempo de ejecución, la segunda requiere que los cambios en su modelo de objetos y homogeniza la salida al denominador común más bajo.

2voto

YYY Puntos 3216

Para lo que vale, acabé teniendo que manejar esto de mí mismo para la mayor parte. Cada objeto tiene un Deserialize(cadena jsonStream) método. Algunos fragmentos de la misma:

JObject parsedJson = this.ParseJson(jsonStream);
object thingyObjectJson = (object)parsedJson["thing"];
this.Thing = new Thingy(Convert.ToString(thingyObjectJson));

En este caso, la nueva Cosita(cadena) es un constructor que va a llamar a la Deserialize(cadena jsonStream) método de la correspondiente tipo concreto. Este esquema continuará a ir hacia abajo y hacia abajo hasta llegar a la base a los puntos que json.NET sólo puede manejar.

this.Name = (string)parsedJson["name"];
this.CreatedTime = DateTime.Parse((string)parsedJson["created_time"]);

Así sucesivamente y así sucesivamente. Esta configuración me permitió dar json.NET las configuraciones se pueden manejar sin tener que rehacer una gran parte de la biblioteca en sí misma o mediante difícil de manejar, tratar/analizar los modelos que se han atascado toda nuestra biblioteca debido a la cantidad de los objetos involucrados. También significa que puedo manejar efectivamente los json cambios en un objeto específico, y que no necesita preocuparse acerca de todo lo que toques de objeto. No es la solución ideal, pero funciona bastante bien de nuestra unidad y las pruebas de integración.

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by: