使用TJSONUnMarshal的自定义注册还原器意外失败 [英] Unexpected failure of custom registered Reverter using TJSONUnMarshal
问题描述
下面的代码来自Marco Cantu的Delphi 2010手册第7章中的JSonMarshall项目。可从此处 http://cc.embarcadero.com/item/27600 获得源代码。我对其进行了两项更改:
The code below is from the JSonMarshall project in chapter 7 of Marco Cantu's Delphi 2010 Handbook. The source code is available from here http://cc.embarcadero.com/item/27600. I have made two changes to it:
-
在实现Uses子句中添加JSon使其得以编译。
Add JSon to the implementation Uses clause to get it to compile.
添加了一行
theName:='XXX'; //由我添加
theName := 'XXX'; // added by me
添加到 TDataWithList.Create
构造函数以协助调试
to the TDataWithList.Create
constructor to assist debugging
我正在西雅图Delphi中运行代码(没有更新1)
I am running the code in Delphi Seattle (without update 1)
该项目将为TDataWithList声明的类型演示自定义转换器和还原器。从结果输出判断为 Memo1
,自定义转换器似乎运行良好。
The purpose of the project is to demo a custom converter and reverter for the TDataWithList declared type. The custom converter seems to work fine, judging by the result output to Memo1
.
但是,尝试运行还原器会在该行上产生读取地址00000000 AV
However, attempting to run the reverter results in a "Read of address 00000000" AV on the line
sList.Add (Args[I]);
在 btnUnmarshalReverterClick
中。造成这种情况的直接原因是,与作者显然想要的
相反,当执行上述行时,sList为Nil。
in btnUnmarshalReverterClick
. The immediate cause of this is that contrary to what
the author evidently intended, when the above line executes, sList is Nil.
我的问题就是为什么sList Nil以及如何解决此问题?
My question is simply why is sList Nil and how to fix this problem?
我尝试(但并非完全成功)通过DBXJSONReflect源
进行跟踪,以找出原因。
I have tried, not entirely successfully, to trace through the DBXJSONReflect source to find out why.
之后
Obj := ObjectInstance(FRTTICtx, objType);
在函数 TJSONUnMarshal.CreateObject
,TDataWithList( obj).theName是我期望的'XXX'
,而TDataWithList(obj).theLList是已初始化但为空的
TStringList。
in function TJSONUnMarshal.CreateObject
, TDataWithList(obj).theName is 'XXX'
as I'd expect and TDataWithList(obj).theLList is an initialized, but empty,
TStringList.
但是,当 btnUnmarshalReverterClick
中的匿名方法被调用时,TDataWithList(Data).theList是 Nil
。
However, by the time the anonymous method in btnUnmarshalReverterClick
is called, TDataWithList(Data).theList is Nil
.
更新: TDataWithList(Data).theList(不正确,imo)变为Nil的原因是在TJSONPopulationCustomizer中将其设置为Nil。通过调用PrePopulateObjField进行PrePopulate。所以我想问题是,为什么PrePopulate允许覆盖已在其构造函数中初始化的对象字段,就好像它更了解该对象的构造函数一样。
Update: The reason that TDataWithList(Data).theList (incorrectly, imo) becomes Nil is that it is set to Nil in TJSONPopulationCustomizer.PrePopulate by a call to PrePopulateObjField. So I suppose the question is, why does PrePopulate allow an object's field which has been initialized in its constructor to be overwritten as if it knows better that the object's constructor.
Update2:
据我所知,可能还有另外一个问题,在
TInternalJSONPopulationCustomizer.PrePopulateObjField
,使用Nil覆盖TListWithData.theList的赋值,即
There may be an additional problem, in that as far as I can tell, in
TInternalJSONPopulationCustomizer.PrePopulateObjField
, the assignment which overwrites TListWithData.theList with Nil, namely
rttiField.SetValue(Data, TValue.Empty);
似乎不会导致TStringlist析构函数被调用。
does not seem to result in the TStringlist destructor being called.
顺便说一句,我在XE4中运行该项目时遇到相同的错误,这是我拥有的最早版本,其中包括JSonUnMarshal。
Btw, I get the same error running the project in XE4, which is the earliest version I have which includes JSonUnMarshal.
代码:
type
[...]
TDataWithList = class
private
theName: String;
theList: TStringList;
public
constructor Create (const aName: string); overload;
constructor Create; overload;
function ToString: string; override;
destructor Destroy; override;
end;
[...]
procedure TFormJson.btnMarshalConverterClick(Sender: TObject);
var
theData: TDataWithList;
jMarshal: TJSONMarshal;
jValue: TJSONValue;
begin
theData := TDataWithList.Create('john');
try
jMarshal := TJSONMarshal.Create(
TJSONConverter.Create); // converter is owned
try
jMarshal.RegisterConverter(TDataWithList, 'theList',
function (Data: TObject; Field: string): TListOfStrings
var
I: Integer;
sList: TStringList;
begin
sList := TDataWithList(Data).theList;
SetLength(Result, sList.Count);
for I := 0 to sList.Count - 1 do
Result[I] := sList[I];
end);
jValue := jMarshal.Marshal(theData);
try
Memo1.Lines.Text := jValue.ToString;
finally
jValue.Free;
end;
finally
jMarshal.Free;
end;
finally
theData.Free;
end;
end;
procedure TFormJson.btnUnmarshalReverterClick(Sender: TObject);
var
jUnmarshal: TJSONUnMarshal;
jValue: TJSONValue;
anObject: TObject;
begin
jValue := TJSONObject.ParseJSONValue(
TEncoding.ASCII.GetBytes (Memo1.Lines.Text), 0);
try
jUnmarshal := TJSONUnMarshal.Create;
try
jUnmarshal.RegisterReverter(TDataWithList, 'theList',
procedure (Data: TObject; Field: string; Args: TListOfStrings)
var
I: Integer;
sList: TStringList;
begin
sList := TDataWithList(Data).theList;
for I := 0 to Length(Args) - 1 do
sList.Add (Args[I]);
end);
anObject := jUnmarshal.Unmarshal(jValue);
try
ShowMessage ('Class: ' + anObject.ClassName +
sLineBreak + anObject.ToString);
finally
anObject.Free;
end;
finally
jUnmarshal.Free;
end;
finally
jValue.Free;
end;
end;
function TMyData.ToString: string;
begin
Result := theName + ':' + IntToStr (theValue);
end;
{ TDataWithList }
constructor TDataWithList.Create(const aName: string);
var
I: Integer;
begin
theName := aName;
theList := TStringList.Create;
for I := 0 to 9 do
theList.Add(IntToStr (Random (1000)));
end;
constructor TDataWithList.Create;
begin
// core initialization, used for default construction
theName := 'XXX'; // added by me
theList := TStringList.Create;
end;
destructor TDataWithList.Destroy;
begin
theList.Free;
inherited;
end;
function TDataWithList.ToString: string;
begin
Result := theName + sLineBreak + theList.Text;
end;
推荐答案
rttiField.SetValue(Data ,TValue.Empty);
只是覆盖字段值,因为顾名思义,它是一个字段,而不是具有get / set方法的属性。由于简单的指针分配,未调用TStringList的析构函数。
rttiField.SetValue(Data, TValue.Empty);
simply overrides the field value because as the name implies it's a field, not a property with get / set methods. The destructor of TStringList is not called due to simple pointer assignment.
此处的解决方案是声明属性:
The solution here is to declare a property:
TDataWithList = class
...
strict private
theList: TStringList;
...
public
property Data: TStringList read ... write SetData
...
end;
TDataWithList.SetData(TStringList aValue);
begin
theList.Assign(aValue);
end;
这篇关于使用TJSONUnMarshal的自定义注册还原器意外失败的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!