2010-10-19 13 views
8

Ich versuche, eine JSon-Datei zu einer Instanz einer Klasse zu deserialisieren, die eine abstrakte Liste enthält. Serialisieren der Instanz in den Json funktioniert gut (überprüfen Sie die JSON-Datei unten). Beim Deserialisieren bekomme ich eine "System.MemberAccessException" mit der Meldung "Kann keine abstrakte Klasse erstellen". Offensichtlich versucht der Deseralizer die abstrakte Klasse und nicht die konkrete Klasse zu instantiieren.JSON-Deseralisierung zur abstrakten Liste mit DataContractJsonSerializer

In meinem Beispiel die entserialisierten Klasse ElementContainer genannt:

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

Dies ist die Json-Datei, die serialisierte war und dass ich deserialisieren versuchen. Beachten Sie das „__type“ Feld für den Deserializer der konkreten Klassen zu erstellen:

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

Im Folgenden ist der Code Ich verwende für die Deserialisierung:

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

Es ist möglich, die Deserialisierung Arbeit zu machen?

Danke!

+0

Haben Sie versucht, den Typ der Liste zu ändern und zu sehen, was passiert? – leppie

+0

Ich habe das versucht, aber es ändert nichts. – noon

Antwort

10

Wir haben gefunden, warum es nicht funktionierte. Kurz nach der Serialisierung des Objekts haben wir die resultierende Zeichenfolge für mehr Lesbarkeit identifiziert. Dann schreiben wir die Zeichenfolge in eine Datei:

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

Die identation tatsächlich der Grund ist, warum die Deserialisierung nicht funktioniert. Es scheint, dass der Parser des DataContractJsonSerializers wirklich wählerisch ist. Wenn einige Zeichen zwischen dem Zeichen {und dem Feld "__type" liegen, geht der Serializer verloren.

Zum Beispiel wird diese Zeichenfolge serialisiert richtig:

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

Aber die nächste Saite wird nicht serialisiert werden.

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

Der einzige Unterschied sind die Leerzeichen vor dem "__type". Die Serialisierung löst eine MemberAccessException aus. Dies ist irreführend, da dieses Verhalten nur beim Deserialisieren in eine abstrakte Liste angezeigt wird. Serialisierung in ein abstraktes Feld funktioniert einwandfrei, unabhängig von den Zeichen.

Um dieses Problem zu beheben, ohne die Lesbarkeit der Datei zu entfernen, kann die Zeichenfolge vor der Deseralisierung geändert werden. Zum Beispiel:

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

Das gemeldete '\\ {[\\ n \\ r] * \" __ type "Muster ist gefährlich, das würde alle Eigenschaften diskriminieren, die vor '__type' serialisiert wurden oder eine Eigenschaft übernehmen, die den Text' "__type" enthält Verwenden von implizitem Raum anstelle expliziter "\ s" für whitespace- und plattformspezifische Newline-Sequenzen. –

+0

Der __type ist wie ein Schlüsselwort für den DataContractJsonSerializer. Es muss vor allen anderen Feldern (und tatsächlich vor allen anderen Zeichen) stehen, sonst wird der JSON nicht im richtigen Typ serialisiert. Was plattformspezifische Zeichen betrifft, werde ich das ändern. Vielen Dank. – noon

+0

Einfach __type als erste Eigenschaft des Objekts für mich gearbeitet. Es ist nicht erforderlich, vor der Deserialisierung durch Regex zu ersetzen. –

Verwandte Themen