2010-10-19 11 views
8

Próbuję deserializować plik JSon do instancji klasy zawierającej listę abstrakcyjną. Serializacja instancji do Json działa dobrze (sprawdź plik json poniżej). Podczas deserializacji otrzymuję "System.MemberAccessException" z komunikatem "Nie można utworzyć klasy abstrakcyjnej". Obvisouly dealizator próbuje stworzyć klasę abstrakcyjną, a nie konkretną klasę.Dezercja JSON do listy abstrakcyjnej przy użyciu DataContractJsonSerializer

W moim przykładzie postaci innej niż serializowana klasa nazywa 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; } 
    } 
} 

Jest to plik JSON, który był w odcinkach i że próbuję deserializowania. Zwróć uwagę na „pole” __type dla Deserializatora do stworzenia klasy betonu:

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

Poniżej znajduje się kod używam dla deserializacji:

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

Jest możliwe, aby prace deserializacjia?

Dzięki!

+0

Czy próbowałeś zmienić typ listy na obiekt i zobaczyć, co się dzieje? – leppie

+0

Próbowałem, ale to nic nie zmienia. – noon

Odpowiedz

10

Znaleźliśmy, dlaczego to nie działa. Bezpośrednio po serializacji obiektu identyfikujemy wynikowy ciąg dla większej czytelności. Następnie zapisujemy ciąg w pliku:

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

Tożsamość jest właśnie powodem, dla którego deserializacja nie działa. Wygląda na to, że parser DataContractJsonSerializer jest naprawdę wybredny. Jeśli niektóre znaki znajdują się między znakiem {a polem "__type", serializator gubi się.

Na przykład ten ciąg będzie szeregować poprawnie:

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

Ale to następny ciąg nie będzie serializacji.

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

Jedyna różnica to znaki spacji przed "__type". Serializacja spowoduje wyjątek MemberAccessException. Jest to wprowadzające w błąd, ponieważ takie zachowanie pojawia się tylko wtedy, gdy deserializing do abstrakcyjnej listy. Serializowanie w abstrakcyjne pole działa dobrze bez względu na bohaterów.

Aby rozwiązać ten problem bez usuwania czytelności pliku, ciąg znaków można zmodyfikować przed dewastracją. Na przykład:

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

Przedstawiony wzorzec '\\ {[\\ n \\ r] * \" __ type' jest niebezpieczny, co dyskryminowałoby wszystkie właściwości zserializowane przed typem "__type" lub pobierało jakąkolwiek właściwość zawierającą tekst "__type", jak również użycie niejawnej przestrzeni zamiast jawnego '\ s' dla nowej i nowej sekwencji newline dla platformy. –

+0

Typ __jest podobny do słowa kluczowego dla DataContractJsonSerializer. Musi on zostać umieszczony przed jakimikolwiek innymi polami (a właściwie przed jakimikolwiek innymi znakami), w przeciwnym razie json nie zostanie zserializowany we właściwym typie. Jeśli chodzi o znaki charakterystyczne dla tablicy, to to zmienię. Dzięki. – noon

+0

Użycie po prostu __type jako pierwszej właściwości obiektu zadziałało dla mnie. Nie ma potrzeby zastępowania Regex przed deserializacją. –

Powiązane problemy