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. Добавьте JSon в реализацию Uses, чтобы заставить его скомпилировать.

  2. Добавил строчку

    theName: = 'XXX'; // добавлено мной

в конструктор TDataWithList.Create для облегчения отладки

Я запускаю код в Delphi Seattle (без обновления 1)

Цель проекта - демонстрация настраиваемого преобразователя и ревертера для объявленного типа TDataWithList. Пользовательский преобразователь вроде работает нормально, судя по выводу результата на Memo1.

Однако попытка запустить ревертер приводит к AV «Чтение адреса 00000000» в строке

           sList.Add (Args[I]);

в btnUnmarshalReverterClick. Непосредственной причиной этого является то, что вопреки тому, что автор, очевидно, имел в виду, что при выполнении указанной выше строки sList будет равен Nil.

Мой вопрос просто: почему sList Nil и как решить эту проблему?

Я попытался, но не совсем успешно, отследить источник DBXJSONReflect чтобы узнать почему.

После

  Obj := ObjectInstance(FRTTICtx, objType);

в функции TJSONUnMarshal.CreateObject, TDataWithList (obj). Имя - 'XXX' как я и ожидал, а TDataWithList (obj) .theLList - инициализированный, но пустой, TStringList.

Однако к моменту вызова анонимного метода в btnUnmarshalReverterClick TDataWithList (Data) .theList будет Nil.

Обновление: Причина, по которой TDataWithList (Data) .theList (неправильно, imo) становится Nil, заключается в том, что для него установлено значение Nil в TJSONPopulationCustomizer.PrePopulate с помощью вызова PrePopulateObjField. Итак, я полагаю, вопрос в том, почему PrePopulate позволяет перезаписывать поле объекта, которое было инициализировано в его конструкторе, как если бы он лучше знал, что конструктор объекта.

Обновление2:

Может быть дополнительная проблема в том, насколько я могу судить, в TInternalJSONPopulationCustomizer.PrePopulateObjField, назначение, которое заменяет TListWithData.theList значением Nil, а именно

rttiField.SetValue(Data, TValue.Empty);

does not seem to result in the TStringlist destructor being called.

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;

MartynA

Ответов: 1

Ответы (1)

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;

2022 WebDevInsider