JSON.NET - comment désérialiser la collection d'instances d'interface?

Je voudrais sérialiser ce code via json.net:

public interface ITestInterface
{
    string Guid {get;set;}
}

public class TestClassThatImplementsTestInterface1
{
    public string Guid { get;set; }
}

public class TestClassThatImplementsTestInterface2
{
    public string Guid { get;set; }
}


public class ClassToSerializeViaJson
{
    public ClassToSerializeViaJson()
    {             
         this.CollectionToSerialize = new List<ITestInterface>();
         this.CollectionToSerialize.add( new TestClassThatImplementsTestInterface2() );
         this.CollectionToSerialize.add( new TestClassThatImplementsTestInterface2() );
    }
    List<ITestInterface> CollectionToSerialize { get;set; }
}

Je veux sérialiser / désérialiser ClassToSerializeViaJson avec json.net. la sérialisation fonctionne, mais la désérialisation me donne cette erreur:

Newtonsoft.Json.JsonSerializationException: impossible de créer une instance de type ITestInterface. Type est une interface ou une classe abstraite et ne peut pas être instanciée.

Alors, comment puis-je désérialiser la collection List<ITestInterface>?

46
demandé sur johnnyRose 2013-04-08 17:37:00

8 réponses

Ci-dessous exemple de travail complet avec ce que vous voulez faire:

public interface ITestInterface
{
    string Guid { get; set; }
}

public class TestClassThatImplementsTestInterface1 : ITestInterface
{
    public string Guid { get; set; }
    public string Something1 { get; set; }
}

public class TestClassThatImplementsTestInterface2 : ITestInterface
{
    public string Guid { get; set; }
    public string Something2 { get; set; }
}

public class ClassToSerializeViaJson
{
    public ClassToSerializeViaJson()
    {
        this.CollectionToSerialize = new List<ITestInterface>();
    }
    public List<ITestInterface> CollectionToSerialize { get; set; }
}

public class TypeNameSerializationBinder : SerializationBinder
{
    public string TypeFormat { get; private set; }

    public TypeNameSerializationBinder(string typeFormat)
    {
        TypeFormat = typeFormat;
    }

    public override void BindToName(Type serializedType, out string assemblyName, out string typeName)
    {
        assemblyName = null;
        typeName = serializedType.Name;
    }

    public override Type BindToType(string assemblyName, string typeName)
    {
        var resolvedTypeName = string.Format(TypeFormat, typeName);
        return Type.GetType(resolvedTypeName, true);
    }
}

class Program
{
    static void Main()
    {
        var binder = new TypeNameSerializationBinder("ConsoleApplication.{0}, ConsoleApplication");
        var toserialize = new ClassToSerializeViaJson();

        toserialize.CollectionToSerialize.Add(
            new TestClassThatImplementsTestInterface1()
            {
                Guid = Guid.NewGuid().ToString(), Something1 = "Some1"
            });
        toserialize.CollectionToSerialize.Add(
            new TestClassThatImplementsTestInterface2()
            {
                Guid = Guid.NewGuid().ToString(), Something2 = "Some2"
            });

        string json = JsonConvert.SerializeObject(toserialize, Formatting.Indented, 
            new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.Auto,
                Binder = binder
            });
        var obj = JsonConvert.DeserializeObject<ClassToSerializeViaJson>(json, 
            new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.Auto,
                Binder = binder 
            });

        Console.ReadLine();
    }
}
31
répondu Piotr Stapp 2017-07-06 07:55:50

J'ai trouvé cette question en essayant de le faire moi-même. Après avoir implémenté la réponse de Garath , j'ai été frappé par la simplicité que cela semblait. Si j'implémentais simplement une méthode qui était déjà passée le Type exact (en tant que chaîne) que je voulais instancier, pourquoi la bibliothèque ne la liait-elle pas automatiquement?

En fait, j'ai trouvé que je n'avais pas besoin de classeurs personnalisés, Json.Net était capable de faire exactement ce dont j'avais besoin, à condition de lui dire que c'était ce que je faisais.

Quand la sérialisation:

string serializedJson = JsonConvert.SerializeObject(objectToSerialize, Formatting.Indented, new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.Objects,
    TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple
});

Lors de la désérialisation:

var deserializedObject = JsonConvert.DeserializeObject<ClassToSerializeViaJson>(serializedJson, new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.Objects
});

La documentation Pertinente: la Sérialisation des Paramètres pour Json.NET et TypeNameHandling réglage

74
répondu Ben Jenkinson 2017-05-23 12:09:37

J'ai également été surpris par la simplicité de Garath, et j'ai également conclu que la bibliothèque Json peut le faire automatiquement. Mais j'ai aussi pensé que c'était encore plus simple que la réponse de Ben Jenkinson (même si je peux voir qu'elle a été modifiée par le développeur de la bibliothèque json lui-même). De mes tests, tout ce que vous devez faire est de définir TypeNameHandling sur Auto, comme ceci:

var objectToSerialize = new List<IFoo>();
// TODO: Add objects to list
var jsonString = JsonConvert.SerializeObject(objectToSerialize, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto });
var deserializedObject = JsonConvert.DeserializeObject<List<IFoo>>(jsonString, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto });
15
répondu Inrego 2015-05-23 00:30:19

En utilisant les paramètres par défaut, vous ne pouvez pas. JSON.NET n'a aucun moyen de savoir comment désérialiser un tableau. Cependant, vous pouvez spécifier le convertisseur de type à utiliser pour votre type d'interface. Pour voir comment faire, voir cette page: http://blog.greatrexpectations.com/2012/08/30/deserializing-interface-properties-using-json-net/

Vous pouvez également trouver des informations sur ce problème à cette question SO: Casting interfaces for deserialization in JSON.NET

8
répondu Erik Schierboom 2017-05-23 11:46:54

C'est une vieille question, mais j'ai pensé ajouter une réponse plus approfondie (sous la forme d'un article que j'ai écrit): http://skrift.io/articles/archive/bulletproof-interface-deserialization-in-jsonnet/

TLDR: plutôt que de configurer Json.NET pour intégrer des noms de type dans le JSON sérialisé, vous pouvez utiliser un convertisseur JSON pour déterminer la classe à désérialiser en utilisant la logique personnalisée que vous aimez.

Cela a l'avantage que vous pouvez refactoriser vos types sans s'inquiéter de la rupture de la désérialisation.

4
répondu Nicholas Westby 2016-05-06 01:44:55

Quasi-duplicata de la réponse D'Inrego, mais cela mérite d'être expliqué plus en détail:

Si vous utilisez TypeNameHandling.Auto alors il inclut uniquement le nom de type / assembly quand a besoin de (c'est-à-dire les interfaces et les classes de base/dérivées). Donc, votre JSON est plus propre, plus petit, plus spécifique.

Quel n'est pas l'un des principaux arguments de vente sur XML/SOAP?

3
répondu sliderhouserules 2016-02-24 21:37:56

Je voulais désérialiser JSON qui n'était pas sérialisé par mon application, donc j'avais besoin de spécifier l'implémentation concrète manuellement. J'ai développé la réponse de Nicholas.

Disons que nous avons

public class Person
{
    public ILocation Location { get;set; }
}

Et l'instance concrète de

public class Location: ILocation
{
    public string Address1 { get; set; }
    // etc
}

Ajouter dans cette classe

public class ConfigConverter<I, T> : JsonConverter
{
    public override bool CanWrite => false;
    public override bool CanRead => true;
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(I);
    }
    public override void WriteJson(JsonWriter writer,
        object value, JsonSerializer serializer)
    {
        throw new InvalidOperationException("Use default serialization.");
    }

    public override object ReadJson(JsonReader reader,
        Type objectType, object existingValue,
        JsonSerializer serializer)
    {
        var jsonObject = JObject.Load(reader);
        var deserialized = (T)Activator.CreateInstance(typeof(T));
        serializer.Populate(jsonObject.CreateReader(), deserialized);
        return deserialized;
    }
}

Définissez ensuite vos interfaces avec L'attribut JsonConverter

public class Person
{
    [JsonConverter(typeof(ConfigConverter<ILocation, Location>))]
    public ILocation Location { get;set; }
}
3
répondu Adam Pedley 2017-02-16 06:18:00

Cela peut être fait avec JSON.NET etjsonsubtypes attributs:

[JsonConverter(typeof(JsonSubtypes))]
[JsonSubtypes.KnownSubTypeWithProperty(typeof(Test1), "Something1")]
[JsonSubtypes.KnownSubTypeWithProperty(typeof(Test2), "Something2")]
public interface ITestInterface
{
    string Guid { get; set; }
}

public class Test1 : ITestInterface
{
    public string Guid { get; set; }
    public string Something1 { get; set; }
}

public class Test2 : ITestInterface
{
    public string Guid { get; set; }
    public string Something2 { get; set; }
}

Et simplement:

var fromCode = new List<ITestInterface>();
// TODO: Add objects to list
var json = JsonConvert.SerializeObject(fromCode);
var fromJson = JsonConvert.DeserializeObject<List<ITestInterface>>(json);
1
répondu manuc66 2018-03-11 19:04:42