2011-05-24 2 views
3

J'utilise DataContractJsonSerializer pour désérialiser des objets d'un service externe. Dans la plupart des cas, cela a très bien fonctionné pour moi. Cependant, il y a un cas où je dois désérialiser JSON qui contient une liste d'objets qui héritent tous de la même classe de base, mais il y a beaucoup de différents types d'objets dans cette liste.Désérialisation d'une liste mixte d'objets à partir de JSON

Je sais que cela peut être fait facilement en incluant une liste de types connus dans le constructeur du sérialiseur, mais je n'ai pas accès au code qui a généré ce service JSON. Les types que j'utilise seront différents des types utilisés dans le service (surtout le nom de classe et l'espace de noms seront différents). En d'autres termes, les classes avec lesquelles les données ont été sérialisées ne seront pas les mêmes que celles que j'utiliserai pour les désérialiser, même si elles seront très similaires.

Avec le XML DataContractSerializer, je peux passer un DataContractResolver pour mapper les types de services à mes propres types, mais il n'y a pas un tel constructeur pour le DataContractJsonSerializer. Y a-t-il un moyen de le faire? Les seules options que j'ai pu trouver sont: écrire mon propre désérialiseur, ou utiliser Microsoft's JsonObject qui n'a pas été testé et "ne devrait pas être utilisé dans des environnements de production".

Voici un exemple:

[DataContract] 
public class Person 
{ 
    [DataMember] 
    public string Name { get; set; } 
} 

[DataContract] 
public class Student : Person 
{ 
    [DataMember] 
    public int StudentId { get; set; } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     var jsonStr = "[{\"__type\":\"Student:#UnknownProject\",\"Name\":\"John Smith\",\"StudentId\":1},{\"Name\":\"James Adams\"}]"; 

     using (var stream = new MemoryStream()) 
     { 
      var writer = new StreamWriter(stream); 
      writer.Write(jsonStr); 
      writer.Flush(); 

      stream.Position = 0; 
      var s = new DataContractJsonSerializer(typeof(List<Person>), new Type[] { typeof(Student), typeof(Person) }); 
      // Crashes on this line with the error below 
      var personList = (List<Person>)s.ReadObject(stream); 
     } 
    } 
} 

Voici l'erreur mentionnée dans le commentaire ci-dessus:

Element ':item' contains data from a type that maps to the name 
'http://schemas.datacontract.org/2004/07/UnknownProject:Student'. The 
deserializer has no knowledge of any type that maps to this name. Consider using 
a DataContractResolver or add the type corresponding to 'Student' to the list of 
known types - for example, by using the KnownTypeAttribute attribute or by adding 
it to the list of known types passed to DataContractSerializer. 

Répondre

1

J'ai trouvé la réponse. C'était très simple. J'ai juste besoin de mettre à jour mon DataContract attribut pour spécifier quel espace de noms (vous pouvez également spécifier un autre nom) dans leur carte la source JSON comme ceci:

[DataContract(Namespace = "http://schemas.datacontract.org/2004/07/UnknownProject")] 
public class Person 
{ 
    [DataMember] 
    public string Name { get; set; } 
} 

[DataContract(Namespace = "http://schemas.datacontract.org/2004/07/UnknownProject"] 
public class Student : Person 
{ 
    [DataMember] 
    public int StudentId { get; set; } 
} 
0

Ce JsonObject était un exemple pour .NET 3.5. Il y a un projet dans codeplex - http://wcf.codeplex.com - qui a une implémentation testée des classes JsonValue/JsonObject/JsonArray/JsonPrimitive, incluant le code source et les tests unitaires. Avec cela, vous pouvez analyser JSON "non typé". Un autre framework JSON bien utilisé est le JSON.NET au http://json.codeplex.com.

0

Vous pouvez créer un DTO avant la sérialisation.

J'utilise une classe comme: (code pseudo)

class JsonDto 

string Content {get;set;} 
string Type {get;set;} 

ctor(object) => sets Content & Type Properties 

static JsonDto FromJson(string) // => Reads a Serialized JsonDto 
           // and sets Content+Type Properties 

string ToJson() // => serializes itself into a json string 

object Deserialize() // => deserializes the wrapped object to its saved Type 
        // using Content+Type properties 

T Deserialize<T>() // deserializes the object as above and tries to cast to T 

Utilisation du JsonDto vous pouvez facilement sérialisation des objets arbitraires JSON et les désérialiser à leur type de base commune parce que le désérialiseur saura toujours le type original et renvoie un type de référence d'objet qui sera casté si vous utilisez la méthode générique Deserialize<T>. Un avertissement: Si vous définissez la propriété Type vous devez utiliser le AssemblyQualifiedName du type, mais sans l'attribut de version (ex: MyCompany.SomeNamespace.MyType, MyCompany.SomeAssembly). Si vous utilisez simplement la propriété AssemblyQualifiedName de la classe Type, vous obtiendrez des erreurs si la version de votre assembly change.

J'ai implémenté un JsonDtoCollection de la même manière, qui dérive de List<JsonDto> et fournit des méthodes pour gérer des collections d'objets.

class JsonDtoCollection : List<JsonDto> 

ctor(List<T>) => wraps all items of the list and adds them to itself 

static JsonDtoCollection FromJson(string) // => Reads a collection of serialized 
              // JsonDtos and deserializes them, 
              // returning a Collection 

string ToJson() // => serializes itself into a json string 

List<object> Deserialize() // => deserializes the wrapped objects using 
          // JsonDto.Deserialize 

List<T> Deserialize<T>() // deserializes the as above and tries to cast to T