2010-10-19 3 views
8

Sto tentando di deserializzare un file JSon in un'istanza di una classe che contiene un elenco astratto. La serializzazione dell'istanza su Json funziona bene (controlla il file json qui sotto). Durante la deserializzazione viene visualizzata una "System.MemberAccessException" con il messaggio "Impossibile creare una classe astratta". Obvisouly il desalizzatore sta tentando di istanziare la classe astratta e non la classe concreta.Deseralizzazione JSON per l'elenco astratto utilizzando DataContractJsonSerializer

Nel mio esempio la classe deserializzato si chiama 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; } 
    } 
} 

Questo è il file JSON che era serializzato e che sto cercando di deserializzare. Notate il campo "__type" per il deserializzatore per creare le classi concrete:

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

Il seguente è il codice che sto utilizzando per deserializzazione:

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

E 'possibile far funzionare il deserializzazione?

Grazie!

+0

Hai provato a cambiare il tipo di elenco da obiettare e vedere cosa sta succedendo? – leppie

+0

L'ho provato ma non cambia nulla. – noon

risposta

10

Abbiamo trovato il motivo per cui non funzionava. Subito dopo la serializzazione dell'oggetto identifichiamo la stringa risultante per una maggiore leggibilità. Quindi scriviamo la stringa in un file:

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

L'identificazione è in realtà il motivo per cui la deserializzazione non funziona. Sembra che il parser di DataContractJsonSerializer sia davvero pignolo. Se alcuni caratteri si trovano tra il carattere {e il campo "__type", il serializzatore viene perso.

Per esempio questa stringa sarà serializzare correttamente:

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

Ma questa nuova stringa non verrà serializzare.

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

L'unica differenza è lo spazio caratteri prima del "__tipo". La serializzazione genererà una MemberAccessException. Questo è fuorviante perché questo comportamento appare solo quando deserializza in una lista astratta. La serializzazione in un campo astratto funziona bene indipendentemente dai personaggi.

Per risolvere questo problema senza rimuovere la leggibilità del file, la stringa può essere modificata prima della deseralizzazione. Ad esempio:

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

Il pattern '\\ {[\\ n \\ r] * \" __ type' pubblicato è pericoloso, che discrimina qualsiasi proprietà serializzata prima di '__type' o preleva qualsiasi proprietà contenente il' '__type' di testo così come utilizzando lo spazio implicito piuttosto che esplicito '\ s' per lo spazio bianco e la sequenza di nuova riga specifica della piattaforma. –

+0

Il __type è come una parola chiave per DataContractJsonSerializer. Deve essere posto prima di ogni altro campo (e in realtà prima di ogni altro carattere) altrimenti il ​​json non viene serializzato nel tipo corretto. Per quanto riguarda i caratteri specifici della lastra, l'ho cambiato. Grazie. – noon

+0

Inserire semplicemente __type come la prima proprietà dell'oggetto ha funzionato per me. Non è necessario sostituire con Regex prima della deserializzazione. –