Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>In addition to the answer to this question, I've posted a workaround to your previous question here: <a href="https://stackoverflow.com/questions/7528829/generics-and-marshal-unmarshal-what-am-i-missing-here/7810038#7810038">Generics and Marshal / UnMarshal. What am I missing here?</a></p> <p>For some reason, using the non-default constructor of the TJsonobject causes the issue in XE2 - using the default constructor "fixed" the problem.</p> <p>First, you need to move your TTestobject to its own unit - otherwise, RTTI won't be able to find/create your object when trying to unmarshal.</p> <pre><code> unit uTestObject; interface uses SysUtils, Classes, Contnrs, Generics.Defaults, Generics.Collections, DbxJson, DbxJsonReflect; type {$RTTI EXPLICIT METHODS([]) PROPERTIES([vcPublished]) FIELDS([vcPrivate])} TTestObject=class(TObject) private aList:TStringList; public constructor Create; overload; constructor Create(list: array of string); overload; constructor Create(list:TStringList); overload; destructor Destroy; override; function Marshal:TJSonObject; class function Unmarshal(value: TJSONObject): TTestObject; published property List: TStringList read aList write aList; end; implementation { TTestObject } constructor TTestObject.Create; begin inherited Create; aList:=TStringList.Create; end; constructor TTestObject.Create(list: array of string); var I:Integer; begin Create; for I:=low(list) to high(list) do begin aList.Add(list[I]); end; end; constructor TTestObject.Create(list:TStringList); begin Create; aList.Assign(list); end; destructor TTestObject.Destroy; begin aList.Free; inherited; end; function TTestObject.Marshal:TJSonObject; var Mar:TJSONMarshal; begin Mar:=TJSONMarshal.Create(); try Mar.RegisterConverter(TStringList, function(Data:TObject):TListOfStrings var I, Count:Integer; begin Count:=TStringList(Data).Count; SetLength(Result, Count); for I:=0 to Count-1 do Result[I]:=TStringList(Data)[I]; end); Result:=Mar.Marshal(Self) as TJSonObject; finally Mar.Free; end; end; class function TTestObject.Unmarshal(value: TJSONObject): TTestObject; var Mar: TJSONUnMarshal; L: TStringList; begin Mar := TJSONUnMarshal.Create(); try Mar.RegisterReverter(TStringList, function(Data: TListOfStrings): TObject var I, Count: Integer; begin Count := Length(Data); Result:=TStringList.Create; for I := 0 to Count - 1 do TStringList(Result).Add(string(Data[I])); end ); //UnMarshal will attempt to create a TTestObject from the TJSONObject data //using RTTI lookup - for that to function, the type MUST be defined in a unit Result:=Mar.UnMarshal(Value) as TTestObject; finally Mar.Free; end; end; end. </code></pre> <p>Also note that the constructor has been overloaded - this allows you to see that the code is functional without pre-pouplating the data in the object during creation.</p> <p>Here is the implementation for the generic class list object</p> <pre><code> unit uTestObjectList; interface uses SysUtils, Classes, Contnrs, Generics.Defaults, Generics.Collections, DbxJson, DbxJsonReflect, uTestObject; type {$RTTI EXPLICIT METHODS([]) PROPERTIES([]) FIELDS([])} TTestObjectList&lt;T:TTestObject,constructor&gt; = class(TObjectList&lt;T&gt;) public function Marshal: TJSonObject; constructor Create; class function Unmarshal(value: TJSONObject): TTestObjectList&lt;T&gt;; static; end; //Note: this MUST be present and initialized/finalized so that //delphi will keep the RTTI information for the generic class available //also, it MUST be "project global" - not "module global" var X:TTestObjectList&lt;TTestObject&gt;; implementation { TTestObjectList&lt;T&gt; } constructor TTestObjectList&lt;T&gt;.Create; begin inherited Create; //removed the add for test data - it corrupts unmarshaling because the data is already present at creation end; function TTestObjectList&lt;T&gt;.Marshal: TJSonObject; var Marshal: TJsonMarshal; begin Marshal := TJSONMarshal.Create; try Marshal.RegisterConverter(TTestObjectList&lt;T&gt;, function(Data: TObject): TListOfObjects var I: integer; begin SetLength(Result,TTestObjectlist&lt;T&gt;(Data).Count); for I:=0 to TTestObjectlist&lt;T&gt;(Data).Count-1 do Result[I]:=TTestObjectlist&lt;T&gt;(Data)[I]; end ); Result := Marshal.Marshal(Self) as TJSONObject; finally Marshal.Free; end; end; class function TTestObjectList&lt;T&gt;.Unmarshal(value: TJSONObject): TTestObjectList&lt;T&gt;; var Mar: TJSONUnMarshal; L: TStringList; begin Mar := TJSONUnMarshal.Create(); try Mar.RegisterReverter(TTestObjectList&lt;T&gt;, function(Data: TListOfObjects): TObject var I, Count: Integer; begin Count := Length(Data); Result:=TTestObjectList&lt;T&gt;.Create; for I := 0 to Count - 1 do TTestObjectList&lt;T&gt;(Result).Unmarshal(TJSONObject(Data[I])); end ); //UnMarshal will attempt to create a TTestObjectList&lt;TTestObject&gt; from the TJSONObject data //using RTTI lookup - for that to function, the type MUST be defined in a unit, //and, because it is generic, there must be a GLOBAL VARIABLE instantiated //so that Delphi keeps the RTTI information avaialble Result:=Mar.UnMarshal(Value) as TTestObjectList&lt;T&gt;; finally Mar.Free; end; end; initialization //force delphi RTTI into maintaining the Generic class information in memory x:=TTestObjectList&lt;TTestObject&gt;.Create; finalization X.Free; end. </code></pre> <p>There are several things that are important to note: If a generic class is created at runtime, RTTI information is NOT kept unless there is a globally accessible object reference to that class in memory. See here: <a href="https://stackoverflow.com/questions/2559049/delphi-rtti-and-tobjectlisttobject">Delphi: RTTI and TObjectList&lt;TObject&gt;</a></p> <p>So, the above unit creates such a variable and leaves it instantiated as discussed in the linked article.</p> <p>The main procedure has been updated that shows both marshaling and unmarshaling the data for both objects:</p> <pre><code> procedure Main; var aTestobj, bTestObj, cTestObj : TTestObject; aList, bList : TTestObjectList&lt;TTestObject&gt;; aJsonObject, bJsonObject, cJsonObject : TJsonObject; s: string; begin aTestObj := TTestObject.Create(['one','two','three','four']); aJsonObject := aTestObj.Marshal; s:=aJsonObject.ToString; Writeln(s); bJsonObject:=TJsonObject.Create; bJsonObject.Parse(BytesOf(s),0,length(s)); bTestObj:=TTestObject.Unmarshal(bJsonObject) as TTestObject; writeln(bTestObj.List.Text); writeln('TTestObject marshaling complete.'); readln; aList := TTestObjectList&lt;TTestObject&gt;.Create; aList.Add(TTestObject.Create(['one','two'])); aList.Add(TTestObject.Create(['three'])); aJsonObject := aList.Marshal; s:=aJsonObject.ToString; Writeln(s); cJSonObject:=TJsonObject.Create; cJSonObject.Parse(BytesOf(s),0,length(s)); bList:=TTestObjectList&lt;TTestObject&gt;.Unmarshal(cJSonObject) as TTestObjectList&lt;TTestObject&gt;; for cTestObj in bList do begin writeln(cTestObj.List.Text); end; writeln('TTestObjectList&lt;TTestObject&gt; marshaling complete.'); Readln; end; </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload