#1 2015-09-18 17:17:51

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

How to get JSON for nested TObjectList ?

Dear ab,

  when a TSQLRecord descendant contains a TObjectList of TSynPersistent descendant, which contains a TObjectList of another TSynPersistent descendant, I could not figure how to get the correct JSON representation of the TSQLRecord. Could you help to comment ? Many thanks !

  When using the code at the end of this post, the incorrect JSON representation is obtained. Specifically, the info of the NestedObject is lost. yikes

{
	"ID": 1,
	"OuterObjects": 
	[{
			"ClassName":"TOuterObject"
		},{
			"ClassName":"TOuterObject"
		}
	]
}
program Project1;

{$APPTYPE CONSOLE}

uses
  FastMM4, 
  SynCommons, SynLog, SynTests, SynSQLite3, SynSQLite3Static, mORMot, mORMotSQLite3,
  Contnrs, SysUtils;

type
  TNestedObject = class(TSynPersistent)
  private
    FDescription: RawUTF8;
  published
    property Description: RawUTF8 read FDescription write FDescription;
  end;

  TNestedObjectList = class(TObjectList)
  protected
    procedure SetObject (Idx: Integer; Item: TNestedObject);
    function GetObject (Idx: Integer): TNestedObject;
  public
    function Add (Obj: TNestedObject): Integer;
    procedure Insert (Idx: Integer; Obj: TNestedObject);
    property Objects [Idx: Integer]: TNestedObject read GetObject write SetObject; default;
  end;

  TOuterObject = class(TSynPersistent)
  private
    FNestedObjects: TNestedObjectList;
  public
    property NestedObjects: TNestedObjectList read FNestedObjects write FNestedObjects;
    constructor Create; override;
    destructor Destroy; override;
  end;

  TOuterObjectList = class(TObjectList)
  protected
    procedure SetObject (Idx: Integer; Item: TOuterObject);
    function GetObject (Idx: Integer): TOuterObject;
  public
    function Add (Obj: TOuterObject): Integer;
    procedure Insert (Idx: Integer; Obj: TOuterObject);
    property Objects [Idx: Integer]: TOuterObject read GetObject write SetObject; default;
  end;

  TSQLMainRecord = class(TSQLRecord)
  private
    FOuterObjects: TOuterObjectList;
  public
    constructor Create; override;
    destructor Destroy; override;  
  published
    property OuterObjects: TOuterObjectList read FOuterObjects write FOuterObjects;
  end;

  TTestManipulateNestedObjectList = class(TSynTestCase)
  published
    procedure AddNestedObjectList;
  end;

  TTestSuite = class(TSynTests)
  published
    procedure MyTestSuite;
  end;

function TNestedObjectList.Add(Obj: TNestedObject): Integer;
begin
  Result := inherited Add (Obj);
end;

procedure TNestedObjectList.Insert(Idx: Integer; Obj: TNestedObject);
begin
  inherited Insert(Idx, Obj);
end;

procedure TNestedObjectList.SetObject(Idx: Integer; Item: TNestedObject);
begin
  inherited SetItem (Idx, Item);
end;

function TNestedObjectList.GetObject(Idx: Integer): TNestedObject;
begin
  Result := inherited GetItem (Idx) as TNestedObject;
end;

constructor TOuterObject.Create;
begin             
  inherited Create;
  FNestedObjects := TNestedObjectList.Create(True);
end;

destructor TOuterObject.Destroy;
begin
  FNestedObjects.Free;
  inherited Destroy;
end;

function TOuterObjectList.Add(Obj: TOuterObject): Integer;
begin
  Result := inherited Add (Obj);
end;

procedure TOuterObjectList.Insert(Idx: Integer; Obj: TOuterObject);
begin
  inherited Insert(Idx, Obj);
end;

procedure TOuterObjectList.SetObject(Idx: Integer; Item: TOuterObject);
begin
  inherited SetItem (Idx, Item);
end;

function TOuterObjectList.GetObject(Idx: Integer): TOuterObject;
begin
  Result := inherited GetItem (Idx) as TOuterObject;
end;

constructor TSQLMainRecord.Create;
begin
  inherited Create;
  FOuterObjects := TOuterObjectList.Create(True);
end;

destructor TSQLMainRecord.Destroy;
begin
  FOuterObjects.Free;
  inherited Destroy;
end;
    
procedure TTestManipulateNestedObjectList.AddNestedObjectList;
var
  ILog: ISynLog;
  DBFileName: string;
  Model: TSQLModel;
  Rest: TSQLRest;
  Rec: TSQLMainRecord;
  RecID: Int64;
  OuterObject: TOuterObject;
  NestedObject: TNestedObject;
begin
  ILog := TSynLog.Enter;

  DBFileName := ChangeFileExt(ChangeFileExt(ExeVersion.ProgramFileName, '') +
    '_' + FormatDateTime('yyyy_mm_dd_hh_nn_ss_zzz', Now),'.db3');
  Model := TSQLModel.Create([TSQLMainRecord]);

  try
    Rest := TSQLRestClientDB.Create(Model, nil, DBFileName, TSQLRestServerDB, False);
    try
      TSQLRestClientDB(Rest).Server.CreateMissingTables;

      Rec := TSQLMainRecord.Create;
      try
        OuterObject := TOuterObject.Create;
        Rec.OuterObjects.Add(OuterObject);
        NestedObject := TNestedObject.Create;
        OuterObject.NestedObjects.Add(NestedObject);
        NestedObject.Description := 'string 1';
        NestedObject := TNestedObject.Create;
        OuterObject.NestedObjects.Add(NestedObject);
        NestedObject.Description := 'string 2';
        
        OuterObject := TOuterObject.Create;
        Rec.OuterObjects.Add(OuterObject);
        NestedObject := TNestedObject.Create;
        OuterObject.NestedObjects.Add(NestedObject); 
        NestedObject.Description := 'string 3';

        RecID := Rest.Add(Rec, True);
        Assert(RecID > 0, 'Error adding the data');

        SynCommons.FileFromString(ObjectToJSON(Rec, [woHumanReadable]), 'Project1.json');
      finally
        Rec.Free;
      end;

      Rec := TSQLMainRecord.Create(Rest, RecID);
      try
        Check(Rec.OuterObjects.Count = 2);
        Check(Rec.OuterObjects[0].NestedObjects.Count = 2);
        Check(Rec.OuterObjects[0].NestedObjects[0].Description = 'string 1');
        Check(Rec.OuterObjects[0].NestedObjects[1].Description = 'string 2');
        Check(Rec.OuterObjects[1].NestedObjects.Count = 1);
        Check(Rec.OuterObjects[1].NestedObjects[0].Description = 'string 3');
      finally
        Rec.Free;
      end;
    finally
      Rest.Free;
    end;
  finally
    Model.Free;
  end;
end;

procedure TTestSuite.MyTestSuite;
begin
  AddCase([TTestManipulateNestedObjectList]);
end;

begin
  TJSONSerializer.RegisterClassForJSON([TOuterObject, TNestedObject]);

  with TSynLog.Family do
  begin
    Level := LOG_VERBOSE;
    PerThreadLog := ptIdentifiedInOnFile;
    RotateFileCount := 5;
    RotateFileSizeKB := 20*1024; // rotate by 20 MB logs
  end;

  with TTestSuite.Create do
  begin
    try
      Run;
      readln;
    finally
      Free;
    end;
  end;
end.

Offline

#2 2015-09-18 17:52:08

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

Re: How to get JSON for nested TObjectList ?

Try to set the woObjectListWontStoreClassName option.

See http://synopse.info/fossil/info/fa71ce10dc722

Offline

#3 2015-09-18 18:21:31

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

Re: How to get JSON for nested TObjectList ?

Thank you for your efforts very much !

The woObjectListWontStoreClassName does not help. The generated JSON looks like below:

{
	"ID": 1,
	"OuterObjects": 
	[{
		},{
		}
	]
}

More importantly, the nested object list can not be persisted by the ORM, as can be seen from the generated .db3 file.
Could you help to comment ?

Offline

#4 2015-09-18 18:46:26

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

Re: How to get JSON for nested TObjectList ?

Use T*ObjArray instead.

Offline

#5 2015-09-19 18:28:46

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

Re: How to get JSON for nested TObjectList ?

It is found that my problem is actually caused by a mistake, i.e., forget to publish properties... It can be solved as shown below:
Thank you very much for your help ! smile

 TOuterObject = class(TSynPersistent)
  private
    FNestedObjects: TNestedObjectList;
  published
    property NestedObjects: TNestedObjectList read FNestedObjects write FNestedObjects;
  public
    constructor Create; override;
    destructor Destroy; override;
  end;

Offline

Board footer

Powered by FluxBB