#1 2017-11-19 13:33:05

ComingNine
Member
Registered: 2010-07-29
Posts: 294

Why cannot deserialize TStrings prop if class inherits TPersistent ?

I am trying to learn about the JSON deserialization inside mORMot. As shown in the code below (and in the compilable code in gist), I am trying to deserialize a class with a published TStrings property from JSON. The deserialization only works if the class inherits from SynCommons.TSynPersistent, and does not work if the class inherits from Classes.TPersistent. Interestingly, the deserialization also does not work if the TSynPersistent code is copied exactly into the .dpr and the class is made to inherit from this copy.

type
{$M+}

  // Exactly the same as in SynCommons.pas
  TSynPersistent = class(TObject)
  ...

//  TSample = class(TPersistent) // Does not work
  TSample = class(TSynPersistent) // Does not work
//  TSample = class(SynCommons.TSynPersistent) // Works
  private
    FContent: TStrings;
  published
    property Content: TStrings read FContent;
  public
//    constructor Create; // if inherits from TPersistent
    constructor Create; override; // if inherits from TSynPersistent or SynCommons.TSynPersistent
    destructor Destroy; override;

  end;
var
  Sample: TSample;
  JsonContentRaw: RawByteString;
  JsonContentUTF8: RawUTF8;
  JsonContentUTF8Buf: PUTF8Char;
  JsonContentValid: Boolean;
begin
    TJSONSerializer.RegisterClassForJSON([TSynPersistent, TSample]);

    Sample := TSample.Create;
    Sample.Content.Add('new line 3');
    SynCommons.FileFromString(ObjectToJSON(Sample, [woHumanReadable, woStoreClassName]), ChangeFileExt(ParamStr(0), '.Sample.json'));
    FreeAndNil(Sample);

    JsonContentRaw := SynCommons.StringFromFile(ChangeFileExt(ParamStr(0), '.Sample.json'));
    JsonContentUTF8 := CurrentAnsiConvert.AnsiToUTF8(JsonContentRaw);
    JsonContentUTF8Buf := @JsonContentUTF8[1];
    Sample := JSONToNewObject(JsonContentUTF8Buf, JsonContentValid) as TSample;
    Writeln(Format('%s', [BoolToStr(JsonContentValid, True)]));
    if JsonContentValid then
      Writeln(Format('%s', [Sample.Content.Text]));
end.

After following into mORMot.pas, it seems that the obvious reason could be that the GetterAddr(Instance) call returns the pointer to the underlying field if the class inherits from SynCommons.TSynPersistent, but returns a pointer to nil if the class inherits from TPersistent or, interestingly, the exact copy of TSynPersistent. Could you help to comment why a pointer to nil is returned if the class inherits from TPersistent or, more interestingly, the exact copy of TSynPersistent ? yikes

function TPropInfo.ClassFromJSON(Instance: TObject; From: PUTF8Char;
  var Valid: boolean; Options: TJSONToObjectOptions): PUTF8Char;
...  
  if GetterIsField then
    // no setter -> use direct in-memory access from getter (if available)
    Field := GetterAddr(Instance) else
    // no setter, nor direct field offset -> impossible to set the instance
    exit;
  result := JSONToObject(Field^,From,Valid,nil,Options);
...  

Offline

#2 2017-11-19 13:50:00

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,669
Website

Re: Why cannot deserialize TStrings prop if class inherits TPersistent ?

IIRC there is an explicit check in SynCommons.pas which check for the TSynPersistent class type.

Anyway, it does not make any sense to create your own class with the very same code.
Just inherit from TSynPersistent.

Offline

#3 2017-11-20 01:17:22

ComingNine
Member
Registered: 2010-07-29
Posts: 294

Re: Why cannot deserialize TStrings prop if class inherits TPersistent ?

ab wrote:

IIRC there is an explicit check in SynCommons.pas which check for the TSynPersistent class type.

Many thanks for your helpful comments !
Indeed the explicit check for TSynPersistent class type is the root reason ! If SynCommons.TSynPersistent is found in the inheritance chain, the TSynPersistentClass(ItemClass).Create call ensures that the correct constructor is called. For TPersistent for example, the ItemClass.Create call goes to TObject.Create, in which case the deserialization will not work unless the field is initialized in the overridden AfterConstruction procedure.


procedure TClassInstance.Init(C: TClass);
begin
  ItemClass := C;
  if C<>nil then
  repeat // this unrolled loop is faster than cascaded if C.InheritsFrom()
    ...
    if C<>TSynPersistent then
    ...
      ItemCreate := cicTSynPersistent;
      exit;
    ...   
function TClassInstance.CreateNew: TObject;
begin
  ...
  case ItemCreate of
    ...
    cicTSynPersistent: begin
      result := TSynPersistentClass(ItemClass).Create;
      exit;
    end;
    ...
    cicTObject: begin
      result := ItemClass.Create;
      exit;
    end;
  ...
end;

Offline

Board footer

Powered by FluxBB