You are not logged in.
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 ?
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
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
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