1

The SuperObject library has a generic method for serializing objects:

type
   TSomeObject = class
   ...
   end;

var
   lJSON       : ISuperObject;
   lContext    : TSuperRttiContext;
   lSomeObject : TSomeObject;
begin
   lSomeObject := TSomeObject.Create;
   lContext := TSuperRttiContext.Create;
   lJSON := lContext.AsJson<TSomeObject>(lSomeObject);

But now I'm dealing with metaclass instances.
This is the object structure:

TJSONStructure = class(TObject);

TReqBase = class(TJSONStructure)
private
   token: Int64;
public
end;

TReqLogin = class(TReqBase)
private
   username,
   password: String;
   module  : Integer;
public
end;


type
   TWebAct = (ttlogin,
              ttsignin);

TReqClass = class of TReqBase;

const
   cWebActStructures: Array[TWebAct] of
   record
      RequestClass : TReqClass;
   end
   = (
      { ttlogin  } (RequestClass: TReqLogin;),
      { ttsignin } (RequestClass: TReqSignIn;)     // Not in definitions above
     ); 

If I now try:

var
   lContext      : TSuperRttiContext;
   lJSON         : ISuperObject;
   lRequestClass : TReqClass;
   lRequestBase  : TReqBase;
begin
   lRequestClass := cWebActStructures[ttlogin].RequestClass;
   lRequestBase := lRequestClass.Create;     // instance of type TReqLogin
   lContext := TSuperRttiContext.Create;
   lJSON := lContext.AsJson<TReqBase>(lRequestBase);

I get the TReqBase serialized: lJSON.AsString = '{"token":-12346789}'
I want to have TReqLogin serialized.
Tried:

  • lContext.AsJson< TReqLogin >(lRequestBase); won't work: incompatible types) [Besides, I would have to list/handle all possible RequestClass types in my routine]

  • lContext.AsJson< lRequestBase.ClassType >(lRequestBase) neither: E2531 Method 'AsJson' requires explicit type argument(s)

Is there any way I can have my lRequestBase serialized as a TReqLogin, TReq... without having to code for them all?

Jan Doggen
  • 8,799
  • 13
  • 70
  • 144
  • Ah, mixing meta classes, SuperObject and generics are we :-D. Btw, you are not dealing with meta class instances. Just with normal instances instantiated through a metaclass. Could you please verify that it is indeed TReqLogin that is being created? If yes, then you need to pass the correct type to the AsJson method. IIRC there is indeed a way to get that from the instance and pass it as the type to the AsJson method, but I need to dig for this on another machine (don't have Delphi on this one). Nice stuff you are working on by the way. – Marjan Venema Aug 08 '13 at 17:58
  • It is indeed a TReqLogin that is being created. And one way to get it from the instance is lRequestBase.ClassType but that won't input to the AsJson<> (2nd bullet point) – Jan Doggen Aug 08 '13 at 18:08
  • Yeah I know. Just fired up the other machine and about to start digging. It is something I have in mind to implement at work as well, so time well spent. – Marjan Venema Aug 08 '13 at 18:16
  • What version of SuperObject are you using? I had to define VER210 in order to get access to TSuperRttiContext and now superobject doesn't compile in XE2 because it still uses TValueData.FHeapData instead of the new FValueData (using the latest 1.2.4 version from https://code.google.com/p/superobject/downloads/list – Marjan Venema Aug 08 '13 at 19:17
  • I have made exactly those changes to 1.2.4, just replaced FHeapData with FValueData. Testapp for JSON (de-)serialization worked fine after that. Oh, and disable the NT_COMPATIBILIY define (IIRC) there is bug with Daylight Savings Time in those routines. Who uses NT anymore? – Jan Doggen Aug 08 '13 at 19:29
  • Ok, thanks, I just created a helper class with a similar generic method as AsJSon and am coming to think that it simply may not be possible using generics (they are after all type safe at compile time). However, in the superobject unit I do see a TSuperObjectHelper (class helper for TObject) that should then be callable on any object returns a ISuperObject reference using Rtti. That should do the trick as well shouldn't it? – Marjan Venema Aug 08 '13 at 19:41
  • My method is `GenericToString(const aSource: T): string` and you can actually call it without specifying the `` bit, just `GenericToString(Source)`. Interesting thing is that it gives back TReqBase as the class when I am absolutely passing in an instance of TReqLogin/TReqSignIn. So for now I am concluding that trying to do this with generics is futile as it seems the generics engine isn't looking at the actual type of the instance but taking the declared type at face value. So I'd go for the TSuperObjectHelper methods. – Marjan Venema Aug 08 '13 at 19:47
  • TSuperObjectHelper.ToJSON does not do the trick either ;-( It just returns the JSON object for the TReqBase, not the TReqLogin. – Jan Doggen Aug 09 '13 at 07:32
  • Even this does not work: if lRequestBase is TReqInfo then begin lReqInfo := lRequestBase as TReqInfo; lJSON := lContext.AsJson(lReqInfo); end; – Jan Doggen Aug 09 '13 at 07:48
  • Actually Jan, the SuperObjectHelper.ToJSon does work. I'll send you an example project by e-mail. – Marjan Venema Aug 09 '13 at 14:02
  • Thank you Marjan, indeed it works. Very silly but I overlooked that the first type I tested the code with **did not have any additional fields** to the base class. So only one field (from the base class) showed up in the .ToJSON.AsString and I concluded incorrectly that the other ;-) fields did not show. Ouch. – Jan Doggen Aug 12 '13 at 09:59

1 Answers1

1

The trick is to not create a TSuperRttiContext and call its AsJSOn method myself, but to immediately use:

lJSON := RequestBase.ToJSON;

The ToJSON method will then create a TSuperRttiContext behind the scenes and handle the necessary conversions.

With a big thank you to Marjan for her help.

Community
  • 1
  • 1
Jan Doggen
  • 8,799
  • 13
  • 70
  • 144