171 votes

Casting interfaces pour la désérialisation de JSON.NET

Je suis en train de mettre en place un lecteur qui se tiendra dans les objets JSON à partir de différents sites web (pensez à l'information de grattage) et de les traduire en objets C#. Je suis actuellement en utilisant JSON.NET pour le processus de désérialisation. Le problème, je suis en cours d'exécution, c'est qu'il ne sait pas comment gérer l'interface des propriétés de niveau dans une classe. Donc, quelque chose de la nature:

public IThingy Thing

Produira l'erreur:

Impossible de créer une instance de type IThingy. Le Type est une interface ou une classe abstraite et ne peut pas être instanciée.

Il est relativement important d'avoir un IThingy par opposition à un Truc depuis le code, je suis en train de travailler sur sont considérées comme sensibles et les tests unitaires est très important. Se moquant des objets atomique des scripts de test n'est pas possible à part entière des objets comme Truc. Ils doivent être d'une interface.

J'ai été penché sur JSON.NET la documentation pour un certain temps maintenant, et les questions que j'ai pu trouver sur ce site ce sont tous de plus d'un an. Toute aide?

Aussi, si il le faut, mon appli est écrit .NET 4.0.

132voto

Mark Meuer Points 1172

@SamualDavis a fourni une solution à une question connexe, que je vais résumer ici.

Si vous avez désérialiser un flux JSON dans une classe concrète qui a les propriétés de l'interface, vous pouvez inclure les classes concrètes en tant que paramètres à un constructeur de la classe! Le NewtonSoft deserializer est assez intelligent pour comprendre qu'il doit utiliser ces classes concrètes pour désérialiser les propriétés.

Voici un exemple:

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; }
}

68voto

Steve Greatrex Points 8466

(Copié à partir de cette question)

Dans le cas où je n'ai pas eu de contrôle sur les entrants JSON (et ne peut donc pas s'assurer qu'il comprend un $type de propriété) j'ai écrit un convertisseur personnalisé qui permet de spécifier explicitement le type de béton:

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

Ce n'utilise la valeur par défaut processus de mise en œuvre de Json.Net tout en spécifiant explicitement le type de béton.

Le code source et une synthèse sont disponibles sur ce blog.

42voto

Eric Boumendil Points 436

Pour activer la désérialisation de plusieurs implémentations d'interfaces, vous pouvez utiliser JsonConverter, mais pas par le biais d'un attribut:

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

DTOJsonConverter cartes de chaque interface avec une mise en œuvre concrète:

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 est nécessaire uniquement pour les deserializer. Le processus de sérialisation est inchangé. L'objet Json n'avez pas besoin d'intégrer des types de béton noms.

Cette SORTE de post propose la même solution une étape plus loin avec un générique JsonConverter.

9voto

mcw0933 Points 865

Deux choses que vous pouvez essayer:

Mettre en œuvre un try/parse modèle:

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();
  }
}

Ou, si vous pouvez le faire dans votre modèle d'objet, de mettre en œuvre un béton de classe de base entre IPerson et votre feuille d'objets, et désérialiser à elle.

Le premier peut éventuellement échouer au moment de l'exécution, la seconde nécessite des modifications à votre modèle d'objet et homogénéise la sortie vers le plus petit dénominateur commun.

4voto

YYY Points 3216

Pour ce que ça vaut, j'ai fini par avoir à gérer de cette me pour la plupart. Chaque objet a une Deserialize(string jsonStream) de la méthode. Quelques extraits de celui-ci:

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

Dans ce cas, le nouveau "Truc" (chaîne de caractères) est un constructeur qui fera appel à la Deserialize(string jsonStream) méthode du type de béton. Ce régime va continuer à aller vers le bas et vers le bas jusqu'à arriver à la base de points que json.NET pouvez simplement gérer.

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

Ainsi de suite et ainsi de suite. Cette configuration m'a permis de donner json.NET des configurations, il peut gérer sans avoir à refactoriser une grande partie de la bibliothèque elle-même ou à l'aide de lourdes essayer/analyser des modèles qui s'enlisent l'ensemble de notre bibliothèque en raison du nombre d'objets impliqués. Cela signifie aussi que je peux gérer tout json changements sur un objet spécifique, et je n'ai pas besoin de vous inquiéter de tout ce que l'objet touche. Ce n'est pas la solution idéale, mais il fonctionne très bien à partir de notre unité et de tests d'intégration.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X