【问题标题】:Why does a deserialized TDictionary not work correctly?为什么反序列化的 TDictionary 不能正常工作?
【发布时间】:2018-03-25 10:58:13
【问题描述】:

我尝试使用标准 delphi 序列化程序对标准 delphi 容器进行序列化/反序列化。

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

没有正确创建字典。 以下是构造函数创建的 dict 外观: [

这里是反序列化创建的字典:

我该如何解决?

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

【问题讨论】:

  • 可以添加 JSON 内容吗?

标签: json delphi serialization json-deserialization tdictionary


【解决方案1】:

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

让我们看一下TDictionary&lt;K,V&gt; 中声明的构造函数。它们是,至少在我的 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&lt;K,V&gt; 不符合要求。

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

以下代码演示:

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

【讨论】:

  • 很好的答案!我尝试使用子类,但反序列化器引发错误:内部:无法实例化类型 Main.TDictionary.
  • 您是否有可能在该单元中禁用了 RTTI?从这里很难说。显然我展示的代码有效。我想我已经回答了这个问题。很难就您的特定背景向您提供更详细的建议,因为我们看不到它。至少你明白发生了什么。
  • 我尝试序列化 TDictionary = class(TDictionary) 但它引发了错误。将 declamation 更改为: TDictionary = class(TDictionary) 有效!我有旧的 Delphi(XE2) 也许这就是原因
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-29
  • 1970-01-01
  • 2019-12-15
  • 1970-01-01
  • 2020-12-31
相关资源
最近更新 更多