使用TJSONUnMarshal自定义注册的Reverter意外失败

时间:2016-01-01 14:02:31

标签: delphi delphi-10-seattle

以下代码来自Marco Cantu的Delphi 2010手册第7章中的JSonMarshall项目。源代码可从此处http://cc.embarcadero.com/item/27600获得。我对它做了两处修改:

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

  2. 添加了一行

    theName:='XXX'; //由我添加

  3. TDataWithList.Create构造函数以协助调试

    我在Delphi Seattle中运行代码(没有更新1)

    该项目的目的是演示TDataWithList声明类型的自定义转换器和恢复器。自定义转换器似乎工作正常,从结果输出到Memo1判断。

    然而,尝试运行回复器导致线路上的“读取地址00000000”AV

               sList.Add (Args[I]);
    
    btnUnmarshalReverterClick中的

    。造成这种情况的直接原因是与此相反 作者明显打算,当上面的行执行时,sList是Nil。

    我的问题是为什么sList Nil以及如何解决这个问题?

    我已经尝试过,但并非完全成功,可以追踪DBXJSONReflect源代码 找出原因。

      Obj := ObjectInstance(FRTTICtx, objType);
    
    函数TJSONUnMarshal.CreateObject中的

    ,TDataWithList(obj).theName是'XXX' 正如我所料和TDataWithList(obj).theLList是一个初始化但空的, TStringList中。

    但是,当调用btnUnmarshalReverterClick中的匿名方法时,TDataWithList(Data).TheList为Nil

    更新: TDataWithList(Data).theList(错误地,imo)变为Nil的原因是它通过调用PrePopulateObjField在TJSONPopulationCustomizer.PrePopulate中设置为Nil。所以我想问题是,为什么PrePopulate允许在其构造函数中初始化的对象字段被覆盖,好像它更好地知道对象的构造函数。

    更新2:

    据我所知,可能存在另外一个问题 TInternalJSONPopulationCustomizer.PrePopulateObjField,用Nil覆盖TListWithData.theList的赋值,即

    rttiField.SetValue(Data, TValue.Empty);
    

    似乎没有导致调用TStringlist析构函数。

    顺便说一下,我在XE4中运行项目得到了同样的错误,这是我最早的版本,包括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;
    

1 个答案:

答案 0 :(得分:3)

rttiField.SetValue(Data, TValue.Empty);只是覆盖字段值,因为顾名思义它是一个字段,而不是具有get / set方法的属性。由于简单的指针赋值,不会调用TStringList的析构函数。

这里的解决方案是声明一个属性:

TDataWithList = class
  ...
  strict private
    theList: TStringList;
    ...
  public
    property Data: TStringList read ... write SetData
    ...
end;

TDataWithList.SetData(TStringList aValue);
begin
  theList.Assign(aValue);
end;