Below is the code from the JSonMarshall project in chapter 7 of the Marco Cantu Delphi 2010 manual. The source code can be found here http://cc.embarcadero.com/item/27600 . I made two changes:
for the TDataWithList.Create constructor to facilitate debugging
I am running code in Delphi Seattle (no update 1)
The goal of the project is to demonstrate a custom converter and converter for the declared type TDataWithList. The custom converter seems to be working fine, judging by the output to Memo1 .
However, an attempt to start the reverser leads to viewing the "Read Address 00000000" AV on the line
sList.Add (Args[I]);
in btnUnmarshalReverterClick . The immediate reason for this is that, contrary to what the author obviously intended when this line is executed, sList is Nil.
My question is simply why sList Nil and how to fix this problem?
I tried, not quite successfully, to trace through the DBXJSONReflect source to find out why.
After
Obj := ObjectInstance(FRTTICtx, objType);
in the function TJSONUnMarshal.CreateObject , TDataWithList (obj) .theName is 'XXX' as I expected, and TDataWithList (obj) .theLList is initialized, but empty, TStringList.
However, at the time of the anonymous method in btnUnmarshalReverterClick , TDataWithList (Data) .theList Nil .
Update: The reason TDataWithList (Data) .theList (false, imo) becomes Nil is because it is set to Nil in TJSONPopulationCustomizer.PrePopulate by calling PrePopulateObjField. Therefore, I assume that the question is why PrePopulate allows overwriting the field of an object that was initialized in its constructor, as if it knew the constructor of the object better.
Update 2:
An additional problem may arise, since, as far as I can tell, in TInternalJSONPopulationCustomizer.PrePopulateObjField , the assignment of which overwrites TListWithData.theList with Nil, namely
rttiField.SetValue(Data, TValue.Empty);
does not seem to result in a call to the TStringlist destructor.
Btw, I get the same error when starting a project in XE4, which is the earliest version that contains JSonUnMarshal.
code:
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;