6

Ich versuche Serialisierung/Deserialisierung Standard-Delphi-Container mit Standard-Delphi-Serializer.Warum funktioniert ein deserialisiertes TDictionary nicht korrekt?

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; 

Es gibt eine Möglichkeit, das Objekt in JSON und umgekehrt umzuwandeln;

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; 

Linie:

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

nicht Wörterbuch korrekt erstellen. Hier ist, wie dict von Konstruktor sieht: [Dictionary created correctly[1]

und hier wird dict von Deserialisierung erstellt: Dictionary deserialized wrong

Wie kann ich es beheben?

Edit: Hier ist JSON Inhalt

{ 
     "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

Können Sie die JSON-Inhalt hinzufügen? – mjn

Antwort

9

Das Problem ist, dass TJSONMarshal das Wörterbuch mit RTTI ist instanziieren. Dies geschieht durch Aufrufen des ersten parameterlosen Konstruktors, den es finden kann. Und das ist leider der Konstruktor, der in TObject definiert ist.

Werfen wir einen Blick auf die in TDictionary<K,V> deklarierten Konstruktoren. Sie sind, zumindest in meiner XE7-Version:

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; 

Alle diese Konstruktoren haben Parameter.

Sie nicht durch die Tatsache, dass Sie

TDictionary<Integer, Integer>.Create 

und eine Instanz mit FComparer zugewiesen schreiben täuschen. Das löst die erste Überladung oben auf und so schreibt der Compiler diesen Code erneut in den Standardparameter ein.

Sie müssen sicherstellen, dass Sie nur Klassen mit parameterlosen Konstruktoren verwenden, die die Klasse ordnungsgemäß instanziieren. Leider passt TDictionary<K,V> nicht zur Rechnung.

Sie können jedoch eine Unterklasse ableiten, die einen parameterlosen Konstruktor einführt, und Ihr Code sollte mit dieser Klasse arbeiten.

Der folgende Code demonstriert:

{$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

Große Antwort! Ich versuchte mit Unterklasse, aber Deserializer Raise Fehler: Intern: Kann Typ Main.TDictionary nicht instanziieren. – aQuu

+0

Ist es möglich, dass RTTI in dieser Einheit deaktiviert ist? Schwer von hier zu sagen. Offensichtlich funktioniert der Code, den ich gezeigt habe. Und ich denke, ich habe die Frage beantwortet. Es ist sehr schwierig, Ihnen detailliertere Ratschläge zu Ihrem speziellen Kontext zu geben, weil wir es nicht sehen können. Du verstehst zumindest, was passiert. –

+0

Ich versuchte, TDictionary = class (TDictionary ) serialisieren zu lassen, aber es erhöhte Fehler. nach Änderung Deklaration zu: TDictionary = Klasse (TDictionary ) funktioniert! Ich habe älter Delphi (XE2) Mayby das ist warum – aQuu

Verwandte Themen