为什么反序列化的TDictionary不能正确工作?

问题描述:

我尝试使用标准的delphi序列化器对标准的delphi容器进行序列化/反序列化。为什么反序列化的TDictionary不能正确工作?

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; 

有一种方法,我将对象转换为JSON,反之亦然;

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; 

行:

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

不会产生字典正确。 这里是如何看起来字典的构造函数创建: [Dictionary created correctly[1]

,这里是由字典创建反序列化: Dictionary deserialized wrong

我怎样才能解决呢?

编辑: 这里是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

,你可以添加JSON的内容? – mjn

的问题是,TJSONMarshal被实例化使用RTTI字典。它通过调用它可以找到的第一个无参数构造函数来实现。可悲的是,这是TObject中定义的构造函数。

让我们来看看在TDictionary<K,V>中声明的构造函数。至少在我的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; 

所有这些构造函数都有参数。

不要你写

TDictionary<Integer, Integer>.Create 

,并与负责FComparer创建一个实例的事实所迷惑。这解决了上面的第一个重载,因此编译器将该代码重新编写为

TDictionary<Integer, Integer>.Create(0) 

填入默认参数。

您需要做的是确保您只使用具有可正确实例化类的无参数构造函数的类。不幸的是TDictionary<K,V>不符合法案。

但是,您可以派生一个引入无参数构造函数的子类,并且您的代码应该可以与该类一起使用。

下面的代码演示:

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

很好的答案!我尝试使用子类,但反序列化器引发错误:内部:无法实例化类型Main.TDictionary 。 – aQuu

+0

是否有可能在该单元中禁用了RTTI?很难从这里说。很显然,我展示的代码是有效的。我想我已经回答了这个问题。因为我们看不到它,所以很难给你提供关于你的特定环境的更详细的建议。至少你明白发生了什么。 –

+0

我试过序列化TDictionary = class(TDictionary )但它引发错误。修改后声明为:TDictionary = class(TDictionary )正常工作!我有老的Delphi(XE2)mayby那就是为什么 – aQuu