2010-10-19 30 views
8

J'essaye de désérialiser un fichier JSon à une instance d'une classe qui contient une liste abstraite. Sérialiser l'instance au Json fonctionne bien (vérifiez le fichier json ci-dessous). Lors de la désérialisation, j'obtiens une "System.MemberAccessException" avec le message "Impossible de créer une classe abstraite". Obvisouly le deseralizer essaye d'instancier la classe abstraite et pas la classe concrète.Désertion de JSON à la liste abstraite en utilisant DataContractJsonSerializer

Dans mon exemple, la classe désérialisé est appelée ElementContainer:

namespace Data 
{ 
    [DataContract] 
    [KnownType(typeof(ElementA))] 
    [KnownType(typeof(ElementB))] 
    public class ElementContainer 
    { 
     [DataMember] 
     public List<Element> Elements { get; set; } 
    } 

    [DataContract] 
    public abstract class Element 
    { 
    } 

    [DataContract] 
    public class ElementA : Element 
    { 
     [DataMember] 
     int Id { get; set; } 
    } 

    [DataContract] 
    public class ElementB : Element 
    { 
     [DataMember] 
     string Name { get; set; } 
    } 
} 

Ceci est le fichier JSON qui a été sérialisé et que je suis en train de désérialiser. Remarquez le champ « __type » pour le désérialiseur pour créer les classes concrètes:

{ 
    "Elements": 
    [ 
     { 
      "__type":"ElementA:#Data", 
      "Id":1 
     }, 
     { 
      "__type":"ElementB:#Data", 
      "Name":"MyName" 
     }  
    ] 
} 

Ce qui suit est le code que je utilise pour désérialisation:

public T LoadFromJSON<T>(string filePath) 
    { 
     try 
     { 
      using (FileStream stream = File.OpenRead(filePath)) 
      { 
       DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T)); 
       T contract = (T)serializer.ReadObject(stream); 
       return contract; 
      } 
     } 
     catch (System.Exception ex) 
     { 
      logger.Error("Cannot deserialize json " + filePath, ex); 
      throw; 
     } 
    } 

Il est possible de faire le travail de désérialisation?

Merci!

+0

Avez-vous essayé de modifier le type de la liste à objet et de voir ce qui se passe? – leppie

+0

J'ai essayé ça mais ça ne change rien. – noon

Répondre

10

Nous avons trouvé pourquoi cela ne fonctionnait pas. Juste après la sérialisation de l'objet, nous identifions la chaîne résultante pour plus de lisibilité. Ensuite, nous écrivons la chaîne dans un fichier:

public void SaveContractToJSON<T>(T contract, string filePath) 
    { 
     using (MemoryStream stream = new MemoryStream()) 
     { 
      DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T)); 
      serializer.WriteObject(stream, contract); 
      string json = Encoding.UTF8.GetString(stream.ToArray()); 
      File.WriteAllText(filePath, json.IndentJSON()); 
     } 
    } 

Le est en fait la indentation raison pour laquelle la désérialisation ne fonctionnait pas. Il semble que l'analyseur de DataContractJsonSerializer est vraiment pointilleux. Si certains caractères sont entre le caractère {et le champ "__type", le sérialiseur est perdu.

Par exemple cette chaîne sérialiser correctement:

"{\"Elements\":[{\"__type\":\"ElementA:#Data\",\"Id\":1}]}" 

Mais cette chaîne suivante ne sera pas sérialisation. La seule différence est le caractère espace avant le "__type". La sérialisation va lancer une exception MemberAccessException. Ceci est trompeur car ce comportement n'apparaît que lors de la désérialisation dans une liste abstraite. Sérialiser dans un champ abstrait fonctionne très bien, peu importe les caractères.

Pour résoudre ce problème sans supprimer la lisibilité du fichier, La chaîne peut être modifiée avant la désertification. Par exemple:

public T LoadContractFromJSON<T>(string filePath) 
    { 
     try 
     { 
      string text = File.ReadAllText(filePath); 
      text = Regex.Replace(text, "\\{[\\n\\r ]*\"__type", "{\"__type"); 
      using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(text))) 
      { 
       DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T)); 
       T contract = (T)serializer.ReadObject(stream); 
       return contract; 
      } 
     } 
     catch (System.Exception ex) 
     { 
      logger.Error("Cannot deserialize json " + filePath, ex); 
      throw; 
     } 
    } 
+0

Le motif '\\ {[\\ n \\ r] * \" __'' affiché est dangereux, discriminant les propriétés sérialisées antérieures à '__type' ou récupérant toute propriété contenant le texte' "__type' ainsi que utiliser l'espace implicite plutôt que "\ s" explicite pour les espaces et la nouvelle ligne spécifique à la plate-forme. –

+0

Le type __ est semblable à un mot clé pour DataContractJsonSerializer. Il doit être placé avant tous les autres champs (et en fait avant tout autre caractère) sinon le json ne sera pas sérialisé dans le bon type. Concernant les personnages spécifiques à la plateforme, je vais changer cela. Merci. – noon

+0

Mettre simplement __type comme première propriété de l'objet a fonctionné pour moi. Pas besoin de remplacer avec Regex avant la désérialisation. –