为什么反序列化的 TDictionary 不能正常工作?
Why does a deserialized TDictionary not work correctly?
我尝试使用标准 delphi 序列化器 serialize/deserialize 标准 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>;
没有正确创建字典。
这是构造函数创建的字典的外观:
[
这里是反序列化创建的字典:
我该如何解决?
编辑:
这里是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
}
}
问题是 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
的实例。这解析为上面的第一个重载,因此编译器 re-writes 代码为
TDictionary<Integer, Integer>.Create(0)
填写默认参数
您需要做的是确保您只使用具有无参数构造函数的 classes,这些构造函数可以正确实例化 class。不幸的是 TDictionary<K,V>
不符合要求。
然而,您可以派生一个引入无参数构造函数的 sub-class,并且您的代码应该适用于 class。
以下代码演示:
{$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.
我尝试使用标准 delphi 序列化器 serialize/deserialize 标准 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>;
没有正确创建字典。
这是构造函数创建的字典的外观:
[
这里是反序列化创建的字典:
我该如何解决?
编辑: 这里是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
}
}
问题是 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
的实例。这解析为上面的第一个重载,因此编译器 re-writes 代码为
TDictionary<Integer, Integer>.Create(0)
填写默认参数
您需要做的是确保您只使用具有无参数构造函数的 classes,这些构造函数可以正确实例化 class。不幸的是 TDictionary<K,V>
不符合要求。
然而,您可以派生一个引入无参数构造函数的 sub-class,并且您的代码应该适用于 class。
以下代码演示:
{$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.