85 votes

À l'aide de Json.NET convertisseurs de désérialiser des propriétés

J'ai une définition de la classe qui contient une propriété qui retourne une interface.

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

    public ISomething Thing { get; set; }
}

En tentant de sérialiser la classe Foo à l'aide de Json.NET me donne un message d'erreur du genre "impossible de créer une instance de type "ISomething'. ISomething peut être une interface ou une classe abstraite."

Est-il Json.NET attribut ou d'un convertisseur qui allait me permettre de spécifier un béton Something classe à utiliser lors de la désérialisation?

87voto

Daniel T. Points 7990

Une des choses que vous pouvez faire avec Json.NET est:

var settings = new JsonSerializerSettings();
settings.TypeNameHandling = TypeNameHandling.Objects;

JsonConvert.SerializeObject(entity, Formatting.Indented, settings);

L' TypeNameHandling drapeau ajouter un $type de la propriété pour le JSON, ce qui permet Json.NET pour savoir quel type de béton il a besoin pour désérialiser l'objet. Cela vous permet de désérialiser un objet tout en remplissant une interface ou une classe de base abstraite.

L'inconvénient, cependant, est que c'est très Json.NET-spécifique. L' $type sera complet, donc si vous êtes à la sérialisation de type info, le deserializer doit être en mesure de le comprendre ainsi.

Documentation: la Sérialisation des Paramètres avec Json.NET

51voto

MrMDavidson Points 1591

Vous pouvez atteindre cet objectif par l'utilisation de la JsonConverter classe. Supposons que vous avez une classe avec une interface bien;

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

Votre JsonConverter est responsable de la sérialisation et de la sérialisation de la propriété sous-jacente;

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

Lorsque vous travaillez avec une Organisation désérialisé via Json.Net le sous-jacent IPerson pour le Propriétaire de la propriété sera de type Tycoon.

39voto

Erhhung Points 199

Au lieu de passer un personnalisé JsonSerializerSettings objet de JsonConvert.SerializeObject() avec le TypeNameHandling.Option objets, comme mentionné précédemment, vous pouvez simplement marquer que l'interface spécifique de la propriété avec un attribut de sorte que le JSON généré ne pas être gonflé avec "$" type de propriétés sur CHAQUE objet:

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

22voto

SamuelDavis Points 830

Dans la version la plus récente de la troisième partie Newtonsoft Json convertisseur vous pouvez définir un constructeur avec un type de béton relatives à la interfacé propriété.

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

Aussi longtemps que quelque Chose implémente ISomething cela devrait fonctionner. Aussi, ne mettez pas un défaut constructeur vide dans le cas où le JSon tente de l'utiliser, vous devez le forcer à utiliser le constructeur contenant le type de béton.

PS. cela vous permet également de faire votre setters privé.

17voto

Bruno Altinet Points 116

A un même problème alors je suis venu avec mon propre Convertisseur qui utilise des types connus argument.

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

J'ai défini deux méthodes d'extension pour la désérialisation et la sérialisation:

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

Vous pouvez définir votre propre façon de comparer et de déterminer les types dans la convertes, je n'utilise que le nom de la classe.

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