跟进我之前的问题: Generics and Marshal / UnMarshal. What am I missing here?
在“第1部分”(上面的链接)中,TOndrej提供了一个很好的解决方案 - 在XE2上失败了。 在这里,我提供了更正的来源来纠正这一点。
我认为需要更多地扩展这个问题。 所以我想听听大家如何做到这一点:
首先 - 要在XE2和XE2更新1上运行源,请进行以下更改:
Marshal.RegisterConverter(TTestObject,
function (Data: TObject): String // <-- String here
begin
Result := T(Data).Marshal.ToString; // <-- ToString here
end
);
为什么? 我能看到的唯一原因必须与XE2有关,因为它有更多可用的RTTI信息。因此它将尝试并封送返回的TObject。 我在这里走在正确的轨道上吗?请随时发表评论。
更重要 - 该示例未实现UnMarshal方法。 如果有人可以制作并在此发布,我会喜欢它: - )
我希望你仍然对这个主题感兴趣。
亲切的问候 比亚内
答案 0 :(得分:2)
除了这个问题的答案之外,我还在此处发布了上一个问题的解决方法:Generics and Marshal / UnMarshal. What am I missing here?
由于某种原因,使用TJsonobject的非默认构造函数会导致XE2中的问题 - 使用默认构造函数“修复”问题。
首先,您需要将TTestobject移动到自己的单元 - 否则,在尝试解组时,RTTI将无法找到/创建对象。
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.
另请注意,构造函数已经过载 - 这使您可以在创建过程中看到代码是有效的,而无需预先设置对象中的数据。
以下是泛型类列表对象
的实现 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.
有几件事需要注意: 如果在运行时创建泛型类,则不保留RTTI信息,除非在内存中存在对该类的全局可访问对象引用。见这里:Delphi: RTTI and 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;
答案 1 :(得分:0)
这是我自己的解决方案。
由于我非常喜欢多态,我实际上也想要一个可以构建到对象层次结构中的解决方案。让我们说TTestObject和TTestObjectList是我们的BASE对象。从那里我们下降到TMyObject和TMyObjectList。此外,我对对象和列表进行了更改 - 为Marshaller / UnMarshaller添加了属性
TMyObject = class(TTestObject) and TMyObjectList<T:TMyObject> = class(TTestObjectList)
有了这个,我们现在介绍一些新问题。 IE浏览器。如何在层次结构中的行之间处理不同类型的编组以及如何将TJsonMarshal和TJsonUnMarshal作为TTestObject和List上的属性处理。
这可以通过在TTestObject级别引入两个新方法来克服。两个类函数称为RegisterConverters和RegisterReverters。然后我们开始将TTestObjectList的编组功能更改为更简单的编组。
对象和List的两个类函数和属性。
class procedure RegisterConverters(aClass: TClass; aMar: TJSONMarshal); virtual;
class procedure RegisterReverters(aClass: TClass; aUnMar: TJSONUnMarshal); virtual;
property Mar: TJSONMarshal read FMar write SetMar;
property UnMar: TJSONUnMarshal read FUnMar write SetUnMar;
List的Marshal函数现在可以这样完成:
function TObjectList<T>.Marshal: TJSONObject;
begin
if FMar = nil then
FMar := TJSONMarshal.Create(); // thx. to SilverKnight
try
RegisterConverters; // Virtual class method !!!!
try
Result := FMar.Marshal(Self) as TJSONObject;
except
on e: Exception do
raise Exception.Create('Marshal Error : ' + e.Message);
end;
finally
ClearMarshal; // FreeAndNil FMar and FUnMar if assigned.
end;
end;
当然我们仍然可以为TTestObject配备一个编组器 - 但是TTestObjectList的Marshal函数不会使用它。这样,在调用TTestObjectList(或后代)的Marshal时,只会创建一个Marshaller。通过这种方式,我们最终只能获得在向后完成所有重建结构时所需的信息 - UnMarshalling: - )
现在这确实有效 - 但我想知道是否有人对此有任何意见?
让我们为TMyTestObject添加一个属性“TimeOfCreation”: 属性TimeOfCreation:TDateTime读取FTimeOfCreation写入FTimeOfCreation;
并在构造函数中设置属性。
FTimeofCreation := now;
然后我们需要一个转换器,所以我们覆盖了TTestObject的虚拟RegisterConverters。
class procedure TMyTestObject.RegisterConverters(aClass: TClass; aMar: TJSONMarshal);
begin
inherited; // instanciate marshaller and register TTestObject converters
aMar.RegisterConverter(aClass, 'FTimeOfCreation',
function(Data: TObject; Field: String): string
var
ctx: TRttiContext;
date: TDateTime;
begin
date := ctx.GetType(Data.ClassType).GetField(Field).GetValue(Data).AsType<TDateTime>;
Result := FormatDateTime('yyyy-mm-dd hh:nn:ss', date);
end);
end;
我最终得到了非常简单的来源,比如使用TTestObject即
aList := TMyTestObjectList<TMyTestObject>.Create;
aList.Add(TMyTestObject.Create(['one','two']));
aList.Add(TMyTestObject.Create(['three']));
s := (aList.Marshal).ToString;
Writeln(s);
现在我已经成功地使用多态进行编组: - )
这也适用于UnMarshalling btw。我正在重建我的FireBird ORM以生成我所有对象的源代码。
目前的OLD版本可以在这里找到: http://code.google.com/p/objectgenerator/ 请记住,它仅适用于FireBird: - )