泛型和元帅/ UnMarshal。我在这里错过了什么?第2部分 :-) [英] Generics and Marshal / UnMarshal. What am I missing here? PART #2 :-)
问题描述
继续我之前的问题:
泛型和元帅/ UnMarshal。在这里我缺少什么?
在part#1(上面的链接)中,TOndrej提供了一个很好的解决方案 - 在XE2上失败了。
在这里,我提供了更正的来源,以纠正这个问题。
我觉得有必要再扩展这个问题。
所以我想听听大家如何做到这一点:首先 - 为了让源代码在XE2和XE2 update 1上运行,请进行以下更改: $ b
/ p>
Marshal.RegisterConverter(TTestObject,
function(Data:TObject):String //< - String here
begin
结果:= T(Data).Marshal.ToString; //< - ToString here
end
);
为什么?
我能看到的必须与XE2相关的唯一原因是有更多的RTTI信息可用。因此它会尝试并将TObject归还。
我在正确的轨道上吗?请随时发表评论。
更重要 - 该示例没有实现UnMarshal方法。
如果任何人都可以制作一个并在此发布,我会喜欢: - )
我希望你对这个主题仍然感兴趣。
亲切的问候
Bjarne
除了这个答案问题,我已经在此处发布了一个解决方案,以解决您之前的问题:泛型和元帅/ UnMarshal。我缺少什么?
由于某些原因,使用TJsonobject的非默认构造函数会导致XE2中的问题 - 使用默认构造函数fixed 这个问题。
首先,你需要将你的TTestobject移动到它自己的单元中 - 否则,当试图解组时,RTTI将无法找到/创建你的对象。
unit uTestObject;
接口
使用
SysUtils,Classes,Contnrs,Generics.Defaults,Generics.Collections,DbxJson,DbxJsonReflect;
$ b $ $ {$ RTTI Expicit Methods([])PROPERTIES([vcPublished])FIELDS([vcPrivate])}
TTestObject = class(TObject)
private
list:TStringList;
public
构造函数创建;超载;
构造函数Create(list:string of array);超载;
构造函数Create(list:TStringList);超载;
析构函数Destroy;覆盖;
函数Marshal:TJSonObject;
类函数Unmarshal(value:TJSONObject):TTestObject;
发布
属性列表:TStringList读取aList写入aList;
end;
实现
{TTestObject}
构造函数TTestObject.Create;
begin
继承创建;
列表:= TStringList.Create;
end;
构造函数TTestObject.Create(list:string of array);
var
I:整数;
begin
创建;
for I:= low(list)to high(list)do
begin
aList.Add(list [I]);
end;
end;
构造函数TTestObject.Create(list:TStringList);
begin
创建;
list.Assign(list);
end;
析构函数TTestObject.Destroy;
begin
列表。免费;
继承;
end;
函数TTestObject.Marshal:TJSonObject;
var
Mar:TJSONMarshal;
begin
Mar:= TJSONMarshal.Create();
尝试
Mar.RegisterConverter(TStringList,
函数(Data:TObject):TListOfStrings
var
I,Count:Integer;
开始
计数:= TStringList(Data).Count;
SetLength(Result,Count);
for I:= 0 to Count-1 do
结果[I]:= TStringList(Data)[I];
end);
结果:= Mar.Marshal(Self)as TJSonObject;
终于
Mar.Free;
end;
end;
类函数TTestObject.Unmarshal(value:TJSONObject):TTestObject;
var
Mar:TJSONUnMarshal;
L:TStringList;
begin
Mar:= TJSONUnMarshal.Create();
尝试
Mar.RegisterReverter(TStringList,
函数(Data:TListOfStrings):TObject
var
I,Count:Integer;
开始
计数:=长度(数据);
结果:= TStringList.Create;
for I:= 0 to Count - 1 do
TStringList(Result).Add(string (Data [I]));
end
);
// UnMarshal将尝试从TJSONObject数据
//使用RTTI查找创建一个TTestObject - 对于这个函数,类型必须在单元中定义
结果:= Mar.UnMarshal (Value)作为TTestObject;
终于
Mar.Free;
end;
end;
结束。
另请注意,构造函数已被重载 - 这使您可以看到代码没有预先 - 在创建过程中向对象中输入数据。
以下是泛型类列表对象的实现
unit uTestObjectList;
接口
使用
SysUtils,Classes,Contnrs,Generics.Defaults,Generics.Collections,
DbxJson,DbxJsonReflect,uTestObject;
类型
{$ RTTI显式方法([])PROPERTIES([])FIELDS([])}
TTestObjectList< T:TTestObject,constructor> = class(TObjectList< T>)
public
函数Marshal:TJSonObject;
构造函数创建;
class function Unmarshal(value:TJSONObject):TTestObjectList< T>;静态的;
end;
//注意:这个必须存在并初始化/最终化,以便
// delphi将保留泛型类可用的
//的RTTI信息,它必须是project global - 不是模块全局
var
X:TTestObjectList< TTestObject>;
实现
{TTestObjectList< T> }
构造函数TTestObjectList< T> .Create;
begin
继承创建;
//删除了测试数据的添加 - 它破坏了反编组,因为数据已经存在于创建
end;
函数TTestObjectList< T> .Marshal:TJSonObject;
var
元帅:TJsonMarshal;
begin
Marshal:= TJSONMarshal.Create;
try
Marshal.RegisterConverter(TTestObjectList< T> ;,
函数(Data:TObject):TListOfObjects
var
I:integer;
开始
SetLength(Result,TTestObjectlist< T>(Data).Count);
for I:= 0 to TTestObjectlist< T>(Data).Count-1 do
Result [I ]:= TTestObjectlist< T>(Data)[I];
end
);
结果:= Marshal.Marshal(Self)as TJSONObject;
终于
Marshal.Free;
end;
end;
类函数TTestObjectList< T> .Unmarshal(value:TJSONObject):TTestObjectList< T>;
var
Mar:TJSONUnMarshal;
L:TStringList;
begin
Mar:= TJSONUnMarshal.Create();
尝试
Mar.RegisterReverter(TTestObjectList< T> ;,
函数(Data:TListOfObjects):TObject
var
I,Count:Integer;
begin
Count:= Length(Data);
Result:= TTestObjectList< T> .Create;
for I:= 0 to Count - 1 do
TTestObjectList< T>(Result ).Unmarshal(TJSONObject(Data [I]));
end
);
// UnMarshal将尝试创建一个TTestObjectList< TTestObject>从TJSONObject数据
//使用RTTI查找 - 对于这个函数,类型必须在单元中定义
//,并且因为它是通用的,所以必须有一个GLOBAL VARIABLE实例化的
//使得Delphi保持RTTI信息可用
结果:= Mar.UnMarshal(Value)as TTestObjectList< T>;
终于
Mar.Free;
end;
end;
初始化
//强制将delphi RTTI保存在内存中的Generic类信息
x:= TTestObjectList< TTestObject> .Create;
敲定
X.Free;
结束。
有以下几点值得注意:
如果在运行时,RTTI信息不会保留,除非在内存中存在对该类的全局可访问对象引用。请参阅:德尔福:RTTI和TObjectList< TObject>
因此,上面的单元创建了这样一个变量,并使其实例化,如链接文章中讨论的那样。
主程序已更新,编组和解编这两个对象的数据:
procedure Main;
var
aTestobj,
bTestObj,
cTestObj:TTestObject;
aList,
bList:TTestObjectList< TTestObject>;
aJsonObject,
bJsonObject,
cJsonObject:TJsonObject;
s:string;
begin
aTestObj:= TTestObject.Create(['one','two','three','four']);
aJsonObject:= aTestObj.Marshal;
s:= aJsonObject.ToString;
Writeln(s);
bJsonObject:= TJsonObject.Create;
bJsonObject.Parse(BytesOf(s),0,length(s));
bTestObj:= TTestObject.Unmarshal(bJsonObject)as TTestObject;
writeln(bTestObj.List.Text);
writeln('TTestObject marshaling complete。');
readln;
aList:= TTestObjectList< TTestObject> .Create;
aList.Add(TTestObject.Create(['one','two']));
aList.Add(TTestObject.Create(['three']));
aJsonObject:= aList.Marshal;
s:= aJsonObject.ToString;
Writeln(s);
cJSonObject:= TJsonObject.Create;
cJSonObject.Parse(BytesOf(s),0,length(s));
bList:= TTestObjectList< TTestObject> .Unmarshal(cJSonObject)as TTestObjectList< TTestObject>;
for cTestObj in bList do
begin
writeln(cTestObj.List.Text);
end;
writeln('TTestObjectList< TTestObject> marshaling complete。');
Readln;
end;
Following up on my earlier question : Generics and Marshal / UnMarshal. What am I missing here?
In "part #1" (the link above) TOndrej provided a nice solution - that failed on XE2. Here I provide corrected source to correct that.
And I feel the need to expand this issue a bit more. So I would like to hear you all how to do this :
First - To get the source running on XE2 and XE2 update 1 make these changes :
Marshal.RegisterConverter(TTestObject,
function (Data: TObject): String // <-- String here
begin
Result := T(Data).Marshal.ToString; // <-- ToString here
end
);
Why ?? The only reason I can see must be related to XE2 is having a lot more RTTI information available. And hence it will try and marshal the TObject returned. Am I on the right track here? Please feel free to comment.
More important - the example does not implement an UnMarshal method. If anyone can produce one and post it here I would love it :-)
I hope that you still have interest in this subject.
Kind Regards Bjarne
In addition to the answer to this question, I've posted a workaround to your previous question here: Generics and Marshal / UnMarshal. What am I missing here?
For some reason, using the non-default constructor of the TJsonobject causes the issue in XE2 - using the default constructor "fixed" the problem.
First, you need to move your TTestobject to its own unit - otherwise, RTTI won't be able to find/create your object when trying to unmarshal.
unit uTestObject;
interface
uses
SysUtils, Classes, Contnrs, Generics.Defaults, Generics.Collections, DbxJson, DbxJsonReflect;
type
{$RTTI EXPLICIT METHODS([]) PROPERTIES([vcPublished]) FIELDS([vcPrivate])}
TTestObject=class(TObject)
private
aList:TStringList;
public
constructor Create; overload;
constructor Create(list: array of string); overload;
constructor Create(list:TStringList); overload;
destructor Destroy; override;
function Marshal:TJSonObject;
class function Unmarshal(value: TJSONObject): TTestObject;
published
property List: TStringList read aList write aList;
end;
implementation
{ TTestObject }
constructor TTestObject.Create;
begin
inherited Create;
aList:=TStringList.Create;
end;
constructor TTestObject.Create(list: array of string);
var
I:Integer;
begin
Create;
for I:=low(list) to high(list) do
begin
aList.Add(list[I]);
end;
end;
constructor TTestObject.Create(list:TStringList);
begin
Create;
aList.Assign(list);
end;
destructor TTestObject.Destroy;
begin
aList.Free;
inherited;
end;
function TTestObject.Marshal:TJSonObject;
var
Mar:TJSONMarshal;
begin
Mar:=TJSONMarshal.Create();
try
Mar.RegisterConverter(TStringList,
function(Data:TObject):TListOfStrings
var
I, Count:Integer;
begin
Count:=TStringList(Data).Count;
SetLength(Result, Count);
for I:=0 to Count-1 do
Result[I]:=TStringList(Data)[I];
end);
Result:=Mar.Marshal(Self) as TJSonObject;
finally
Mar.Free;
end;
end;
class function TTestObject.Unmarshal(value: TJSONObject): TTestObject;
var
Mar: TJSONUnMarshal;
L: TStringList;
begin
Mar := TJSONUnMarshal.Create();
try
Mar.RegisterReverter(TStringList,
function(Data: TListOfStrings): TObject
var
I, Count: Integer;
begin
Count := Length(Data);
Result:=TStringList.Create;
for I := 0 to Count - 1 do
TStringList(Result).Add(string(Data[I]));
end
);
//UnMarshal will attempt to create a TTestObject from the TJSONObject data
//using RTTI lookup - for that to function, the type MUST be defined in a unit
Result:=Mar.UnMarshal(Value) as TTestObject;
finally
Mar.Free;
end;
end;
end.
Also note that the constructor has been overloaded - this allows you to see that the code is functional without pre-pouplating the data in the object during creation.
Here is the implementation for the generic class list object
unit uTestObjectList;
interface
uses
SysUtils, Classes, Contnrs, Generics.Defaults, Generics.Collections,
DbxJson, DbxJsonReflect, uTestObject;
type
{$RTTI EXPLICIT METHODS([]) PROPERTIES([]) FIELDS([])}
TTestObjectList<T:TTestObject,constructor> = class(TObjectList<T>)
public
function Marshal: TJSonObject;
constructor Create;
class function Unmarshal(value: TJSONObject): TTestObjectList<T>; static;
end;
//Note: this MUST be present and initialized/finalized so that
//delphi will keep the RTTI information for the generic class available
//also, it MUST be "project global" - not "module global"
var
X:TTestObjectList<TTestObject>;
implementation
{ TTestObjectList<T> }
constructor TTestObjectList<T>.Create;
begin
inherited Create;
//removed the add for test data - it corrupts unmarshaling because the data is already present at creation
end;
function TTestObjectList<T>.Marshal: TJSonObject;
var
Marshal: TJsonMarshal;
begin
Marshal := TJSONMarshal.Create;
try
Marshal.RegisterConverter(TTestObjectList<T>,
function(Data: TObject): TListOfObjects
var
I: integer;
begin
SetLength(Result,TTestObjectlist<T>(Data).Count);
for I:=0 to TTestObjectlist<T>(Data).Count-1 do
Result[I]:=TTestObjectlist<T>(Data)[I];
end
);
Result := Marshal.Marshal(Self) as TJSONObject;
finally
Marshal.Free;
end;
end;
class function TTestObjectList<T>.Unmarshal(value: TJSONObject): TTestObjectList<T>;
var
Mar: TJSONUnMarshal;
L: TStringList;
begin
Mar := TJSONUnMarshal.Create();
try
Mar.RegisterReverter(TTestObjectList<T>,
function(Data: TListOfObjects): TObject
var
I, Count: Integer;
begin
Count := Length(Data);
Result:=TTestObjectList<T>.Create;
for I := 0 to Count - 1 do
TTestObjectList<T>(Result).Unmarshal(TJSONObject(Data[I]));
end
);
//UnMarshal will attempt to create a TTestObjectList<TTestObject> from the TJSONObject data
//using RTTI lookup - for that to function, the type MUST be defined in a unit,
//and, because it is generic, there must be a GLOBAL VARIABLE instantiated
//so that Delphi keeps the RTTI information avaialble
Result:=Mar.UnMarshal(Value) as TTestObjectList<T>;
finally
Mar.Free;
end;
end;
initialization
//force delphi RTTI into maintaining the Generic class information in memory
x:=TTestObjectList<TTestObject>.Create;
finalization
X.Free;
end.
There are several things that are important to note: If a generic class is created at runtime, RTTI information is NOT kept unless there is a globally accessible object reference to that class in memory. See here: Delphi: RTTI and TObjectList<TObject>
So, the above unit creates such a variable and leaves it instantiated as discussed in the linked article.
The main procedure has been updated that shows both marshaling and unmarshaling the data for both objects:
procedure Main;
var
aTestobj,
bTestObj,
cTestObj : TTestObject;
aList,
bList : TTestObjectList<TTestObject>;
aJsonObject,
bJsonObject,
cJsonObject : TJsonObject;
s: string;
begin
aTestObj := TTestObject.Create(['one','two','three','four']);
aJsonObject := aTestObj.Marshal;
s:=aJsonObject.ToString;
Writeln(s);
bJsonObject:=TJsonObject.Create;
bJsonObject.Parse(BytesOf(s),0,length(s));
bTestObj:=TTestObject.Unmarshal(bJsonObject) as TTestObject;
writeln(bTestObj.List.Text);
writeln('TTestObject marshaling complete.');
readln;
aList := TTestObjectList<TTestObject>.Create;
aList.Add(TTestObject.Create(['one','two']));
aList.Add(TTestObject.Create(['three']));
aJsonObject := aList.Marshal;
s:=aJsonObject.ToString;
Writeln(s);
cJSonObject:=TJsonObject.Create;
cJSonObject.Parse(BytesOf(s),0,length(s));
bList:=TTestObjectList<TTestObject>.Unmarshal(cJSonObject) as TTestObjectList<TTestObject>;
for cTestObj in bList do
begin
writeln(cTestObj.List.Text);
end;
writeln('TTestObjectList<TTestObject> marshaling complete.');
Readln;
end;
这篇关于泛型和元帅/ UnMarshal。我在这里错过了什么?第2部分 :-)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!