web-dev-qa-db-fra.com

Classe NewtonSoft.Json Serialize et Deserialize avec la propriété de type IEnumerable <ISomeInterface>

J'essaie de déplacer du code pour utiliser les données Json générées par l'API Web ASP.NET MVC au lieu de SOAP Xml.

J'ai rencontré un problème avec la sérialisation et la désérialisation des propriétés de type:

IEnumerable<ISomeInterface>.

Voici un exemple simple:

public interface ISample{
  int SampleId { get; set; }
}
public class Sample : ISample{
  public int SampleId { get; set; }
}
public class SampleGroup{
  public int GroupId { get; set; }
  public IEnumerable<ISample> Samples { get; set; }
 }
}

Je peux facilement sérialiser des instances de SampleGroup avec:

var sz = JsonConvert.SerializeObject( sampleGroupInstance );

Cependant, la désérialisation correspondante échoue:

JsonConvert.DeserializeObject<SampleGroup>( sz );

avec ce message d'exception:

"Impossible de créer une instance de type JsonSerializationExample.ISample. Type est une classe d'interface ou abstraite et ne peut être instancié."

Si je dérive un convertisseur Json, je peux décorer ma propriété comme suit:

[JsonConverter( typeof (SamplesJsonConverter) )]
public IEnumerable<ISample> Samples { get; set; }

Voici le JsonConverter: 

public class SamplesJsonConverter : JsonConverter{
  public override bool CanConvert( Type objectType ){
    return ( objectType == typeof (IEnumerable<ISample>) );
  }

  public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer ){
    var jA = JArray.Load( reader );
    return jA.Select( jl => serializer.Deserialize<Sample>( new JTokenReader( jl ) ) ).Cast<ISample>( ).ToList( );
  }

  public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer ){
    ... What works here?
  }
}

Ce convertisseur résout le problème de désérialisation, mais je ne vois pas comment coder la méthode WriteJson pour que la sérialisation fonctionne à nouveau.

Quelqu'un peut aider? 

Est-ce une façon "correcte" de résoudre le problème en premier lieu?

60
AndyDBell

Vous n'avez pas besoin d'utiliser JsonConverterAttribute, gardez votre modèle propre, utilisez également CustomCreationConverter, le code est plus simple:

public class SampleConverter : CustomCreationConverter<ISample>
{
    public override ISample Create(Type objectType)
    {
        return new Sample();
    }
}

Ensuite:

var sz = JsonConvert.SerializeObject( sampleGroupInstance );
JsonConvert.DeserializeObject<SampleGroup>( sz, new SampleConverter());

Documentation: Désérialiser avec CustomCreationConverter

65
cuongle

C’est assez simple et avec le support fourni par json.net, il vous suffit d’utiliser les JsonSettings suivants pour la sérialisation et la désérialisation:

JsonConvert.SerializeObject(graph,Formatting.None, new JsonSerializerSettings()
{
    TypeNameHandling =TypeNameHandling.Objects,
    TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple
});

et pour la désérialisation, utilisez le code ci-dessous: 

JsonConvert.DeserializeObject(Encoding.UTF8.GetString(bData),type,
    new JsonSerializerSettings(){TypeNameHandling = TypeNameHandling.Objects}
);

Notez simplement l’initialiseur d’objet JsonSerializerSettings, qui est important pour vous. 

17
Sunil S

J'ai résolu ce problème en utilisant un paramètre spécial pour JsonSerializerSettings qui s'appelle TypeNameHandling.All

Le paramètre TypeNameHandling inclut des informations de type lors de la sérialisation de JSON et des informations de type en lecture, de sorte que les types de création soient créés lors de la désérialisation de JSON.

Sérialisation:

var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };
var text = JsonConvert.SerializeObject(configuration, settings);

Désérialisation:

var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };
var configuration = JsonConvert.DeserializeObject<YourClass>(json, settings);

La classe YourClass peut avoir n'importe quel type de champs de type de base et sera sérialisée correctement.

10
adam.bielasty

Excellente solution, merci! J'ai pris la question d'AndyDBell et la réponse de Cuong Le pour construire un exemple avec l'implémentation de deux interfaces différentes:

public interface ISample
{
    int SampleId { get; set; }
}

public class Sample1 : ISample
{
    public int SampleId { get; set; }
    public Sample1() { }
}


public class Sample2 : ISample
{
    public int SampleId { get; set; }
    public String SampleName { get; set; }
    public Sample2() { }
}

public class SampleGroup
{
    public int GroupId { get; set; }
    public IEnumerable<ISample> Samples { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        //Sample1 instance
        var sz = "{\"GroupId\":1,\"Samples\":[{\"SampleId\":1},{\"SampleId\":2}]}";
        var j = JsonConvert.DeserializeObject<SampleGroup>(sz, new SampleConverter<Sample1>());
        foreach (var item in j.Samples)
        {
            Console.WriteLine("id:{0}", item.SampleId);
        }
        //Sample2 instance
        var sz2 = "{\"GroupId\":1,\"Samples\":[{\"SampleId\":1, \"SampleName\":\"Test1\"},{\"SampleId\":2, \"SampleName\":\"Test2\"}]}";
        var j2 = JsonConvert.DeserializeObject<SampleGroup>(sz2, new SampleConverter<Sample2>());
        //Print to show that the unboxing to Sample2 preserved the SampleName's values
        foreach (var item in j2.Samples)
        {
            Console.WriteLine("id:{0} name:{1}", item.SampleId, (item as Sample2).SampleName);
        }
        Console.ReadKey();
    }
}

Et une version générique sur le SampleConverter:

public class SampleConverter<T> : CustomCreationConverter<ISample> where T: new ()
{
    public override ISample Create(Type objectType)
    {
        return ((ISample)new T());
    }
}
4
Daniel Melo

Dans mes projets, ce code fonctionnait toujours comme un sérialiseur par défaut, qui sérialise la valeur spécifiée comme s'il n'y avait pas de convertisseur spécial:

serializer.Serialize(writer, value);
2
fero

J'ai eu ceci au travail:

conversion explicite

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
                                    JsonSerializer serializer)
    {
        var jsonObj = serializer.Deserialize<List<SomeObject>>(reader);
        var conversion = jsonObj.ConvertAll((x) => x as ISomeObject);

        return conversion;
    }
1
dvr

Ayant cela:

public interface ITerm
{
    string Name { get; }
}

public class Value : ITerm...

public class Variable : ITerm...

public class Query
{
   public IList<ITerm> Terms { get; }
...
}

J'ai réussi l'astuce de conversion en implémentant cela:

public class TermConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var field = value.GetType().Name;
        writer.WriteStartObject();
        writer.WritePropertyName(field);
        writer.WriteValue((value as ITerm)?.Name);
        writer.WriteEndObject();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
        JsonSerializer serializer)
    {
        var jsonObject = JObject.Load(reader);
        var properties = jsonObject.Properties().ToList();
        var value = (string) properties[0].Value;
        return properties[0].Name.Equals("Value") ? (ITerm) new Value(value) : new Variable(value);
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof (ITerm) == objectType || typeof (Value) == objectType || typeof (Variable) == objectType;
    }
}

Cela me permet de sérialiser et de désérialiser en JSON comme: 

string JsonQuery = "{\"Terms\":[{\"Value\":\"This is \"},{\"Variable\":\"X\"},{\"Value\":\"!\"}]}";
...
var query = new Query(new Value("This is "), new Variable("X"), new Value("!"));
var serializeObject = JsonConvert.SerializeObject(query, new TermConverter());
Assert.AreEqual(JsonQuery, serializeObject);
...
var queryDeserialized = JsonConvert.DeserializeObject<Query>(JsonQuery, new TermConverter());
0
Stanislav Trifan