#1 2014-11-12 11:20:27

jsmart
Member
Registered: 2014-09-19
Posts: 8

Deserialize nested TObjectList

If I use [woStoreClassName] when serializing an object which contains a property/field of type TObjectList it correctly serializes to Json as expected using ObjectToJson.

Normally, when deserializing, you would pass in the expected class name (assuming no custom serializer registered) to tell JsonToObject what class to create the objects of. 

However, with nested List objects, there's no opportunity to do that as JsonToObject will call (TPropInfo)P^.ClassFromJSON.  This in turn will ultimately recurse back into JsonToObject but always passes nil as the class to construct the list objects for.  Is there anyway to let it read the class name that is stored in the Json string or am I forced to use a custom serializer with these types of objects?

A simple example:

  TMyObj2 = class(TPersistentWithCustomCreate)
  private
    FTesting: string;
  public
    constructor Create; override;
  published
    property Testing: string read FTesting write FTesting;
  end;

  TMyObject = class(TPersistentWithCustomCreate)
  private
    FAValue1: string;
    FAValue2: string;
    FAValue3: Integer;
    FMyObj2: TMyObj2;
    FNextLevel: TNextLevel;
    FMyObj2List: TObjectList;
  public
    constructor Create; override;
    destructor Destroy; override;
    function SetCreate(AValue1, AValue2: string; AValue3: Integer): TMyObject;
  published
    property MyObj2: TMyObj2 read FMyObj2;
    property NextLevel: TNextLevel read FNextLevel;
    property AValue1: string read FAValue1 write FAValue1;
    property AValue2: string read FAValue2 write FAValue2;
    property AValue3: Integer read FAValue3 write FAValue3;
    property MyObj2List: TObjectList read FMyObj2List;
  end;


{ TMyObject }

constructor TMyObject.Create;
begin
  FMyObj2 := TMyObj2.Create;
  FNextLevel := TNextLevel.Create;
  FMyObj2List := TObjectList.Create;
end;

function TMyObject.SetCreate(AValue1, AValue2: string; AValue3: Integer): TMyObject;
begin

  FAValue1 := AValue1;
  FAValue2 := AValue2;
  FAValue3 := AValue3;
  Result := Self;
end;


destructor TMyObject.Destroy;
begin
  FMyObj2List.Free;
  FNextLevel.Free;
  FMyObj2.Free;

  inherited;
end;

procedure TestmORMot_NestedObjectList;
var
  List: TObjectList;
  Json: RawUTF8;
  Valid: Boolean;
  MyObj2: TMyObj2;
begin
  List := TObjectList.Create;
  try
    List.Add(TMyObject.Create.SetCreate('a','b', 1));
    List.Add(TMyObject.Create.SetCreate('c','d', 2));
    TMyObject(List[0]).MyObj2.Testing := 'Testing 1';
    TMyObject(List[1]).MyObj2.Testing := 'Testing 2';

    MyObj2 := TMyObj2.Create;
    MyObj2.Testing := 'My Obj Item 1';
    TMyObject(List[0]).MyObj2List.Add(MyObj2);
    MyObj2 := TMyObj2.Create;
    MyObj2.Testing := 'My Obj Item 2';
    TMyObject(List[0]).MyObj2List.Add(MyObj2);

    Json := ObjectToJson(List);
    Writeln(Utf8ToString(Json));

  finally
    List.Free;
  end;

  Writeln('===================================================');

  List := TObjectList.Create;
  try
    JsonToObject(List, @Json[1], Valid, TMyObject);
    if valid then
    begin
      Writeln('Testing is - ' + TMyObject(List[1]).MyObj2.Testing);
      Writeln(List.Count);
    end
    else
      Writeln('Not Valid');
  finally
    List.Free;
  end;
end;

This will result in this Json:
[{"ClassName":"TMyObject","MyObj2":{"ClassName":"TMyObj2","Testing":"Testing 1"},"NextLevel":{"ClassName":"TNextLevel","NextLevelDesc":"","TopLevelDesc":""},"AValue1":"a","AValue2":"b","AValue3":1,"MyObj2List":[{"ClassName":"TMyObj2","Testing":"My Obj Item "},{"ClassName":"TMyObj2","Testing":"My Obj Item 2"}]},{"ClassName":"TMyObject","MyObj2":{"ClassName":"TMyObj2","Testing":"Testing 2"},"NextLevel":"ClassName":"TNextLevel","NextLevelDesc":"","TopLevelDesc":""},"AValue1":"c","AValue2":"d","AValue3":2,"MyObj2List":[]}]
===================================================

But will always return not valid as it doesn't know what class to use in the MyObjList2 field.

Thanks

Offline

#2 2014-11-12 12:01:02

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

Re: Deserialize nested TObjectList

As documented, just add

  TJSONSerializer.RegisterClassForJSON(TMyObj2);

So that the TObjectList item classnames should be recognized.

The JSONToObject() documentation was pretty clear IMHO:

// - won't handle TObjectList (even if ObjectToJSON is able to serialize
// them) since has now way of knowing the object type to add (TCollection.Add
// is missing), unless: 1. you set the TObjectListItemClass property as expected,
// and provide a TObjectList object, or 2. woStoreClassName option has been
// used at ObjectToJSON() call and the corresponding classes have been previously
// registered by TJSONSerializer.RegisterClassForJSON() (or Classes.RegisterClass)

Offline

#3 2020-12-06 16:26:44

leus
Member
Registered: 2012-09-05
Posts: 79

Re: Deserialize nested TObjectList

Hello,

Is this the case still? I'm trying to serialize a TObjectList but have a hard time understanding this.

Offline

#4 2020-12-06 16:44:52

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

Re: Deserialize nested TObjectList

Yes, the limitation - coming from TObjectLIst itself - will remain for ever.

Offline

Board footer

Powered by FluxBB