#1 2015-09-19 11:18:45

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

How to customize JSON serializer for nested record array from text ?

Dear ab,
  when a TSQLRecord descendant contains an array of record, which contains another array of record, I could not figure how to customize JSON serializer for nested record array from text . Could you help to comment ? Many thanks !
 
  When using the code at the end of this post, the exception is raised as shown below:

Project Project1.exe raised exception class ESynException with message 
'Unregistered ptCustom for TJSONRecordTextDefinition.AddItem(NestedRecordArray: TNESTEDRECORDARRAY)'. Process stopped. 
program Project1;

{$APPTYPE CONSOLE}

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

type
  TNestedRecord = packed record
    Description: RawUTF8;
  end;
  TNestedRecordArray = array of TNestedRecord;

const
  __TNestedRecord = 'Description: RawUTF8';
  __TNestedRecordArray = '[Description: RawUTF8]';

type
  TOuterRecord = packed record
    NestedRecordArray: TNestedRecordArray;
  end;   
  TOuterRecordArray = array of TOuterRecord;

const
  __TOuterRecord = 'NestedRecordArray: TNestedRecordArray';
      
type
  TSQLMainRecord = class(TSQLRecord)
  private
    FOuterRecordArray: TOuterRecordArray;
  published
    property OuterRecordArray: TOuterRecordArray read FOuterRecordArray write FOuterRecordArray;
  end;

  TTestManipulateNestedRecordArray = class(TSynTestCase)
  published
    procedure Test;
  end;

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

procedure TTestManipulateNestedRecordArray.Test;
var
  ILog: ISynLog;
  DBFileName: string;
  Model: TSQLModel;
  Rest: TSQLRest;
  Rec: TSQLMainRecord;
  RecID: Int64;
  NestedRecord: TNestedRecord;
  OuterRecord: TOuterRecord;
  GroupA: TDynArray;
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
        RecordClear(OuterRecord, TypeInfo(TOuterRecord));
        GroupA.Init(TypeInfo(TNestedRecordArray), OuterRecord.NestedRecordArray);

        RecordClear(NestedRecord, TypeInfo(TNestedRecord));
        NestedRecord.Description := 'string 1';
        GroupA.Add(NestedRecord);

        RecordClear(NestedRecord, TypeInfo(TNestedRecord));
        NestedRecord.Description := 'string 2';
        GroupA.Add(NestedRecord);

        Rec.DynArray('OuterRecordArray').Add(OuterRecord);

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

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

      Rec := TSQLMainRecord.Create(Rest, RecID);
      try
        Check(Rec.DynArray('OuterRecordArray').Count = 1);
        Check(Length(Rec.OuterRecordArray[0].NestedRecordArray) = 2);
        Check(Rec.OuterRecordArray[0].NestedRecordArray[0].Description = 'string 1');
        Check(Rec.OuterRecordArray[0].NestedRecordArray[1].Description = 'string 2');
      finally
        Rec.Free;
      end;
    finally
      Rest.Free;
    end;
  finally
    Model.Free;
  end;
end;

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

begin
  TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TNestedRecord), __TNestedRecord);
  // makes no difference
  // TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TNestedRecordArray), __TNestedRecordArray);
  TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TOuterRecord), __TOuterRecord);

  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-19 11:28:52

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

Re: How to customize JSON serializer for nested record array from text ?

__TNestedRecord is indeed enough to define both the record and the dynamic array.

Try

TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TNestedRecordArray), __TNestedRecord);

Offline

#3 2015-09-19 11:35:06

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

Re: How to customize JSON serializer for nested record array from text ?

Thank you for your efforts to help !

I still get the same exception:

Project Project1.exe raised exception class ESynException with message 
'Unregistered ptCustom for TJSONRecordTextDefinition.AddItem(NestedRecordArray: TNESTEDRECORDARRAY)'. Process stopped. 

Could you please help me with this problem ?

Offline

#4 2015-09-19 13:33:54

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

Re: How to customize JSON serializer for nested record array from text ?

Dear ab,

the problem can be described with a much shorter program as shown below.

Could you please help to comment whether the second call to TJSONCustomParserRTTI.CreateFromTypeName should return nil by design ?
If not, could you please help me with this problem ?

program Project2;

{$APPTYPE CONSOLE}

uses
  FastMM4, 
  SynCommons, // mORMot, 
  Contnrs, SysUtils;

type
  TNestedRecord = packed record
    Description: RawUTF8;
  end;
  TNestedRecordArray = array of TNestedRecord;

const
  __TNestedRecord = 'Description: RawUTF8';
  __TNestedRecordArray = '[Description: RawUTF8]';

var
  Parser: TJSONCustomParserRTTI;
begin
  TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TNestedRecord), __TNestedRecord);
  // Make no difference.
  // TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TNestedRecordArray), __TNestedRecordArray);
  // TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TNestedRecordArray), __TNestedRecord);

  // Parser created !
  Parser := TJSONCustomParserRTTI.CreateFromTypeName('NestedRecord', 'TNestedRecord');
  Assert(Assigned(Parser), 'Parser is nil');

  // Parser = nil !
  Parser := TJSONCustomParserRTTI.CreateFromTypeName('NestedRecordArray', 'TNestedRecordArray');
  Assert(Assigned(Parser), 'Parser is nil');
end.

Offline

#5 2015-09-19 14:18:03

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

Re: How to customize JSON serializer for nested record array from text ?

Do not use type names, but TypeInfo(TNestedRecordArray) as input.
It would identify the nested record type of the dynamic array.

Offline

#6 2015-09-19 14:40:58

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

Re: How to customize JSON serializer for nested record array from text ?

Dear ab, I do not think I made the situation clear to you.

The direct cause of the problem described in the first post, i.e., failure of the line below:

 TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TOuterRecord), __TOuterRecord); 

, is the failure of the line below (which is isolated and shown in the fourth post):

 TJSONCustomParserRTTI.CreateFromTypeName('NestedRecordArray', 'TNestedRecordArray'); 

This latter line is in your library, i.e., SynCommons.pas.

Therefore, I do not see how I can use TypeInfo(TNestedRecordArray) as input.

I wonder if you could be kind enough to try the sample code as in the first post ?

Last edited by ComingNine (2015-09-19 14:41:46)

Offline

#7 2015-09-19 17:33:30

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

Re: How to customize JSON serializer for nested record array from text ?

Dear ab,

I now understand your design principle better.

Specifically, for my problem, both calls below will register a "global parser" with the key "TNestedRecord". That is to say, there will not be a "global parser" with the name ''TNestedRecordArray".

TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TNestedRecord), __TNestedRecord);
TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TNestedRecordArray), __TNestedRecord);

The problem can be solved by modifying the definition of the layout of the TOuterRecord as shown below:

type
  TOuterRecord = packed record
    NestedRecordArray: TNestedRecordArray;
  end;   

const
  // __TOuterRecord = 'NestedRecordArray: TNestedRecordArray';
  __TOuterRecord = 'NestedRecordArray: array of TNestedRecord';

Thank you very much for your efforts to help ! smile

Offline

Board footer

Powered by FluxBB