2017-02-10 47 views
6

Próbuję serializować/deserializować standardowy kontener delphi przy użyciu standardowego serializatora delphi.Dlaczego deserializowany TDictionary nie działa poprawnie?

procedure TForm7.TestButtonClick(Sender: TObject); 
var 
    dict: TDictionary<Integer, Integer>; 
    jsonValue: TJSONValue; 
begin 
    //serialization 
    dict := TDictionary<Integer, Integer>.Create; 
    dict.Add(1, 1); 
    jsonValue := TJsonConverter.ObjectToJSON(dict); 
    dict.Free; 

    //deserialization 
    dict := TJsonConverter.JSONToObject(jsonValue) as TDictionary<Integer, Integer>; 
    try 
     Assert(dict.ContainsKey(1), 'deserialization error - key not found'); 
    except 
     Assert(false, 'deserialization error - dict object broken'); 
    end; 
end; 

Istnieje sposób, w jaki konwertuję obiekt na JSON i odwrotnie;

class function TJsonConverter.JSONToObject(AJSONValue: TJSONValue): TObject; 
var 
    lUnMarshal: TJSONUnMarshal; 
begin 
    lUnMarshal := TJSONUnMarshal.Create(); 
    try 
     Result := lUnMarshal.Unmarshal(AJSONValue); 
    finally 
     lUnMarshal.Free; 
    end; 
end; 

class function TJsonConverter.ObjectToJSON(AData: TObject): TJSONValue; 
var 
    lMarshal: TJSONMarshal; 
begin 
    lMarshal := TJSONMarshal.Create(); 

    try 
     Result := lMarshal.Marshal(AData); 
    finally 
     lMarshal.Free; 
    end; 
end; 

linia:

dict := TJsonConverter.JSONToObject(jsonValue) as TDictionary<Integer, Integer>; 

nie tworzy słownika prawidłowo. Oto jak wygląda DICT tworzyć przez konstruktora: [Dictionary created correctly[1]

i tutaj jest DICT stworzony przez deserializacji: Dictionary deserialized wrong

Jak mogę to naprawić?

Edit: Oto zawartość JSON

{ 
     "type" : "System.Generics.Collections.TDictionary<System.Integer,System.Integer>", 
     "id" : 1, 
     "fields" : { 
      "FItems" : [ 
      [ -1, 0, 0 ], 
      [ -1, 0, 0 ], 
      [ -1, 0, 0 ], 
      [ 911574339, 1, 1 ] 
      ], 
      "FCount" : 1, 
      "FGrowThreshold" : 3, 
      "FKeyCollection" : null, 
      "FValueCollection" : null 
     } 
    } 
+0

można dodać zawartość JSON? – mjn

Odpowiedz

9

Problemem jest to, że jest TJSONMarshal instancji słownika przy użyciu RTTI. Czyni to poprzez wywołanie pierwszego konstruktora bez parametrów, który może znaleźć. I, niestety, jest to konstruktor zdefiniowany w TObject.

Rzućmy okiem na konstruktorów zadeklarowanych w TDictionary<K,V>. Są one, przynajmniej w mojej wersji XE7:

constructor Create(ACapacity: Integer = 0); overload; 
constructor Create(const AComparer: IEqualityComparer<TKey>); overload; 
constructor Create(ACapacity: Integer; const AComparer: IEqualityComparer<TKey>); overload; 
constructor Create(const Collection: TEnumerable<TPair<TKey,TValue>>); overload; 
constructor Create(const Collection: TEnumerable<TPair<TKey,TValue>>; 
    const AComparer: IEqualityComparer<TKey>); overload; 

Wszystkie te konstruktory mają parametry.

Nie daj się nabrać na fakt, że piszesz

TDictionary<Integer, Integer>.Create 

i utworzyć instancję z FComparer przypisany. To rozwiązuje pierwsze pierwsze przeciążenie powyżej, więc kompilator ponownie zapisuje ten kod jako

Należy upewnić się, że używane są tylko klasy, które mają konstruktory bez parametrów, które prawidłowo tworzą instancję klasy. Niestety TDictionary<K,V> nie pasuje do rachunku.

Można jednak wyprowadzić podklasę, która wprowadza konstruktor bez parametrów, a twój kod powinien działać z tą klasą.

Poniższy kod demonstruje:

{$APPTYPE CONSOLE} 

uses 
    System.SysUtils, 
    System.Generics.Collections, 
    System.Rtti; 

type 
    TDictionary<K,V> = class(System.Generics.Collections.TDictionary<K,V>) 
    public 
    constructor Create; 
    end; 

{ TDictionary<K, V> } 

constructor TDictionary<K, V>.Create; 
begin 
    inherited Create(0); 
end; 

type 
    TInstance<T: class> = class 
    class function Create: T; static; 
    end; 

class function TInstance<T>.Create: T; 
// mimic the way that your JSON marshalling code instantiates objects 
var 
    ctx: TRttiContext; 
    typ: TRttiType; 
    mtd: TRttiMethod; 
    cls: TClass; 
begin 
    typ := ctx.GetType(TypeInfo(T)); 
    for mtd in typ.GetMethods do begin 
    if mtd.HasExtendedInfo and mtd.IsConstructor then 
    begin 
     if Length(mtd.GetParameters) = 0 then 
     begin 
     cls := typ.AsInstance.MetaclassType; 
     Result := mtd.Invoke(cls, []).AsType<T>; 
     exit; 
     end; 
    end; 
    end; 
    Result := nil; 
end; 

var 
    Dict: TDictionary<Integer, Integer>; 

begin 
    Dict := TInstance<TDictionary<Integer, Integer>>.Create; 
    Dict.Add(0, 0); 
    Writeln(BoolToStr(Dict.ContainsKey(0), True)); 
    Readln; 
end. 
+0

Świetna odpowiedź! Próbowałem z podklasą, ale deserializer podniósł błąd: Wewnętrzny: Nie można utworzyć instancji typu Main.TDictionary . – aQuu

+0

Czy to możliwe, że w tym urządzeniu masz wyłączone RTTI? Trudno powiedzieć stąd. Oczywiście kod, który pokazałem, działa. I myślę, że odpowiedziałem na to pytanie. Bardzo trudno jest udzielić bardziej szczegółowych porad na temat konkretnego kontekstu, ponieważ nie możemy go zobaczyć. Przynajmniej ty rozumiesz, co się dzieje. –

+0

i Próbowałem serializować TDictionary = klasa (TDictionary ), ale wystąpił błąd. po deklacji zmian do: TDictionary = class (TDictionary ) działa! Mam starszą wersję Delphi (XE2), może dlatego, – aQuu