使用TJSONUnMarshal的自定义注册还原器意外失败 [英] Unexpected failure of custom registered Reverter using TJSONUnMarshal

查看:117
本文介绍了使用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:


  1. 在实现Uses子句中添加JSon使其得以编译。

  1. 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆