#1 2016-05-10 23:01:59

ertank
Member
Registered: 2016-03-16
Posts: 168

Json serialization of a class

Hello,

I tried to read more about how mORMot handles JSON in online documentation. I couldn't find an answer myself. If I have below definitions in my code can I serialize "TStDepartment" class using mORMot json function(s)? Or, Is it better I prepare a record definition of it somehow?

type
  TCurrencyType = (
    CURRENCY_NONE = 0,
    CURRENCY_TL = 949,
    CURRENCY_DOLAR = 840,
    CURRENCY_EU = 978,
    CURRENCY_GPR = 826
  );
  
  TItemUnitTypes = (
    ITEM_NONE,
    ITEM_NUMBER = 1,
    ITEM_KILOGRAM = 2,
    ITEM_GRAM = 3,
    ITEM_LITRE = 4,

    // Adetsel Birimler
    ITEM_DUZINE = 11,
    ITEM_DEMET, // 12
    ITEM_KASA, // 13
    ITEM_BAG, // 14

    // Agirlik Birimler
    ITEM_MILIGRAM = 31,
    ITEM_TON, // 32
    ITEM_ONS, // 33
    ITEM_DESIGRAM, // 34
    ITEM_SANTIGRAM, // 35
    ITEM_POUND, // 36
    ITEM_KENTAL, // 37

    // Uzunluk Birimler
    ITEM_METRE = 51,
    ITEM_SANTIMETRE, // 52
    ITEM_MILIMETRE, // 53
    ITEM_DEKAMETRE, // 54
    ITEM_HEKTAMETRE, // 55
    ITEM_KILOMETRE, // 56
    ITEM_DESIMETRE, // 57
    ITEM_MIKRON, // 58
    ITEM_INC, // 59
    ITEM_FOOT, // 60
    ITEM_YARD, // 61
    ITEM_MIL, // 62

    // Hacim Birimler
    ITEM_METREKUP = 71,
    ITEM_DESIMETREKUP, // 72
    ITEM_SANTIMETREKUP, // 73
    ITEM_MILIMETREKUP, // 74
    ITEM_DEKALITRE, // 75
    ITEM_HEKTOLITRE, // 76
    ITEM_KILOLITRE, // 77
    ITEM_DESILITRE, // 78
    ITEM_SANTILITRE, // 79
    ITEM_MILILITRE, // 80
    ITEM_INCKUP, // 81
    ITEM_GALLON, // 82
    ITEM_BUSHEL, // 83

    // Alan Birimler
    ITEM_METREKARE = 91,
    ITEM_DEKAMETREKARE, // 92
    ITEM_AR, // 93
    ITEM_KILOMETREKARE, // 94
    ITEM_DESIMETREKARE, // 95
    ITEM_SANTIMETREKARE, // 96
    ITEM_MILIMETREKARE, // 97
    ITEM_DONUM, // 98
    ITEM_HEKTAR, // 99
    ITEM_INCKARE // 100
  );

  TStDepartment = class
  private
    FDeptName: string;
    FU8TaxIndex: Byte;
    FCurrencyType: TCurrencyType;
    FUnitType: TItemUnitTypes;
    FU64Limit: UInt64;
    FU64Price: UInt64;
  public
    constructor Create();
  public
    property DeptName: string read FDeptName write FDeptName;
    property U8TaxIndex: Byte read FU8TaxIndex write FU8TaxIndex;
    property CurrencyType: TCurrencyType read FCurrencyType write FCurrencyType;
    property UnitType: TItemUnitTypes read FUnitType write FUnitType;
    property U64Limit: UInt64 read FU64Limit write FU64Limit;
    property U64Price: UInt64 read FU64Price write FU64Price;
  end;
  
{ TStDepartment }

constructor TStDepartment.Create;
begin
  FDeptName := '';
end;

Offline

#2 2016-05-11 01:01:10

igors233
Member
Registered: 2012-09-10
Posts: 241

Re: Json serialization of a class

> I tried to read more about how mORMot handles JSON in online documentation. I couldn't find an answer myself.

Start with this few articles, then go to SynCommons and read comments for those functions and also check in samples for examples.

http://blog.synopse.info/post/2011/02/2 … ialization
http://blog.synopse.info/post/2013/01/1 … ialization
http://blog.synopse.info/post/2014/05/1 … anced-RTTI


> If I have below definitions
> in my code can I serialize "TStDepartment" class using mORMot json function(s)?

Yes, that's easy, important thing when serializing classes (unless you write your custom serialization function) is to put properties you want under published section, so in your case it would be:

  TStDepartment = class
   ...
  published
    property DeptName: string read FDeptName write FDeptName;
    property U8TaxIndex: Byte read FU8TaxIndex write FU8TaxIndex;
    property CurrencyType: TCurrencyType read FCurrencyType write FCurrencyType;
    property UnitType: TItemUnitTypes read FUnitType write FUnitType;
    property U64Limit: UInt64 read FU64Limit write FU64Limit;
    property U64Price: UInt64 read FU64Price write FU64Price;
  end;

and code itself:

var
  json: RawUTF8;
  dep: TStDepartment;
begin
  dep := TStDepartment.Create;
  json := SynCommons.ObjectToJSON(dep, [woHumanReadable, woDontStoreDefault]);
  ShowMessage(json);
  dep.Free;
end;

Depending on what you need you can try various params for ObjectToJson second argument (TTextWriterWriteObjectOption).

> Or, Is it better I prepare a record definition of it somehow?

It all depends on what you need, where you'll use it and how.

Offline

#3 2016-05-11 08:21:36

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

Re: Json serialization of a class

Some points when serializing a class:

1. Define your fields as "published"
2. To have the needed RTTI for "published" fields, you need to inherit from TPersistent (or even better TSynPersistent, which is slightly faster and has a virtual constructor you may override)
3. As an alternative, you may defined {$M+} ... {$M-} around the class definition (but inheriting from TSynPersitent may be a better approach)
4. For nested classes (i.e. class instances within a class instance), consider using TSynAutoCreateFields which would initialize and finalize any internal published class instances in its constructor/destructor
5. For nested list/arrays of classes, consider defining T*ObjArray types, register them and use ObjArray*() functions: TSynAutoCreateFields would also recognize such published fields and hold them.
6. As an alternative for list/arrays of classes, you may use a TCollection (as detailed in the doc) but from our POV it is more verbose and less efficient.

Search for all those TSynPersistent / TSynAutoCreateFields keywords in the documentation.

Offline

#4 2016-05-11 16:24:59

ertank
Member
Registered: 2016-03-16
Posts: 168

Re: Json serialization of a class

ab wrote:

Some points when serializing a class:

5. For nested list/arrays of classes, consider defining T*ObjArray types, register them and use ObjArray*() functions: TSynAutoCreateFields would also recognize such published fields and hold them.

Search for all those TSynPersistent / TSynAutoCreateFields keywords in the documentation.


I have searched documentation. Found part explaining TSynAutoCreateFields. Though, It is beyond my understanding. So, I thought it would be easier for me to provide an example and ask here. Staring with a simpler one since I might get it to my understanding and solve remaining myself.

I have following definitions:

  TST_TAX_RATE = record
    taxRate: UInt16;
  end;

And, I need to define that record as an array:

procedure TForm5.Button3Click(Sender: TObject);
var
  Json: AnsiString; // DLL call needs me sending AnsiString as parameters
  stTaxRates: Array [1..8] of TST_TAX_RATE;
begin
  // Serialize
  Json := AnsiString(RecordSaveJson(stTaxRates, TypeInfo(TST_TAX_RATE)));
  Memo1.Lines.Add(String(Json));
  Exit;
end;

When I run, above code displays following in Memo1:

{"taxRate":0}

What I expect to read in it is something like:

[{"taxRate":0},{"taxRate":0},{"taxRate":0},{"taxRate":0},{"taxRate":0},{"taxRate":0},{"taxRate":0},{"taxRate":0}]

I didn't understand where I am making a mistake here.

Offline

#5 2016-05-11 16:34:18

igors233
Member
Registered: 2012-09-10
Posts: 241

Re: Json serialization of a class

Try with DynArraySaveJSON instead of RecordSaveJSON.

Offline

#6 2016-05-11 16:40:24

ertank
Member
Registered: 2016-03-16
Posts: 168

Re: Json serialization of a class

Changed related part in my code to:

Json := AnsiString(DynArraySaveJSON(stDept, TypeInfo(TST_DEPARTMENT)));

What I received was:

{"taxRate":0}

Thanks for your help. Unfortunately, that didn't solve it.

Offline

#7 2016-05-12 16:24:43

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

Re: Json serialization of a class

And if you just set EnumSetsAsText=TRUE to DynArraySaveJSON?
wink

Offline

#8 2016-05-12 16:49:30

ertank
Member
Registered: 2016-03-16
Posts: 168

Re: Json serialization of a class

Latest version of the code:

  TST_TAX_RATE = record
    taxRate: UInt16;
  end;

procedure TForm5.Button3Click(Sender: TObject);
var
  Json: AnsiString; // DLL call needs me sending AnsiString as parameters
  stTaxRates: Array [1..8] of TST_TAX_RATE;
begin
  // Serialize
  Json := AnsiString(DynArraySaveJSON(stTaxRates, TypeInfo(TST_TAX_RATE), True));
  Memo1.Lines.Add(String(Json));
  Exit;
end;

my result still is

{"taxRate":0}

sad



EDIT: Just downloaded latest nightly build. Result is still same.

Last edited by ertank (2016-05-12 17:26:00)

Offline

#9 2016-05-12 18:20:12

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

Re: Json serialization of a class

1. taxRate is an UInt16, i.e. a "word".
This is not an enumerate, so TypeInfo(TST_TAX_RATE) would just find a record with a word within!

2. You are mixing dynamic arrays and fixed arrays (Array [1..8]) this won't work...

3. You are passing TypeInfo(TST_TAX_RATE)  which is the RTTI info for a record, not a dynamic array.

Offline

#10 2016-05-12 18:57:38

ertank
Member
Registered: 2016-03-16
Posts: 168

Re: Json serialization of a class

I very much appreciate correction of my code, please.

Offline

#11 2016-05-12 21:09:21

ertank
Member
Registered: 2016-03-16
Posts: 168

Re: Json serialization of a class

Tried another code. This is not working for me, too.

  TStTaxRate = record
    taxRate: UInt16;
  end;
  TStTaxRates = Array of TStTaxRate;

procedure TForm5.Button3Click(Sender: TObject);
var
  Json: AnsiString;
  stTaxRates: TStTaxRates;
begin
  SetLength(stTaxRates, 8);
  // Serialize
  Json := AnsiString(DynArraySaveJSON(stTaxRates, TypeInfo(TStTaxRates)));
  Memo1.Lines.Add(String(Json));
  Exit;
end;

TSTTaxRates is an array type in above code. Result on screen is:

[0,0,0,0,0,0,0,0]

This is close to what I need, however still missing variable name in serialized string. How can I change my code to get below result:

[{"taxRate":0},{"taxRate":0},{"taxRate":0},{"taxRate":0},{"taxRate":0},{"taxRate":0},{"taxRate":0},{"taxRate":0}]

Thanks.

Last edited by ertank (2016-05-12 21:15:38)

Offline

#12 2016-05-13 08:32:36

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

Re: Json serialization of a class

Define your TStTaxRate record as text using e.g.

  TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TStTaxRate), 'taxrate word');

Offline

#13 2016-05-13 09:40:37

ertank
Member
Registered: 2016-03-16
Posts: 168

Re: Json serialization of a class

Hi Ab,

That code solved my problem. Thank you. I was starting to afraid that I won't be able to solve it in 1-2 lines of coding before I saw your post.

Should I use it always just before calling DynArraySaveJSON? Or, it is just fine to call once in the application (for example OnCreate event) and that is enough?

My working code is as follows:

  TStTaxRate = packed record
    taxRate: string;
  end;
  TStTaxRates = Array of TStTaxRate;

procedure TForm5.Button3Click(Sender: TObject);
var
  Json: AnsiString;
  stTaxRates: TStTaxRates;
begin
  TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TStTaxRates), 'taxRate word');
  SetLength(stTaxRates, 8);
//   Serialize
  Json := AnsiString(DynArraySaveJSON(stTaxRates, TypeInfo(TStTaxRates)));
  Memo1.Lines.Add('from mORMot: ' + String(Json));
  Exit;
end;

Memo1 display:

from mORMot: [{"taxRate":0},{"taxRate":0},{"taxRate":0},{"taxRate":0},{"taxRate":0},{"taxRate":0},{"taxRate":0},{"taxRate":0}]

That is exactly what I needed.

Last edited by ertank (2016-05-13 09:42:58)

Offline

#14 2016-05-13 10:33:03

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

Re: Json serialization of a class

RTFM TTextWriter.RegisterCustomJSONSerializerFromText() is to be called once.

Offline

#15 2016-05-13 11:10:37

ertank
Member
Registered: 2016-03-16
Posts: 168

Re: Json serialization of a class

Sorry, couldn't find it in Documentation "10.1.3.2.4. Text-based definition" section. Is it written somewhere else?

Offline

#16 2016-05-13 11:14:24

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

Re: Json serialization of a class

Why on earth should it be called more than once?
Is there any example in the doc which state the contrary?
This is a registration purpose, to be done once.

Offline

#17 2016-05-13 13:12:56

ertank
Member
Registered: 2016-03-16
Posts: 168

Re: Json serialization of a class

Using version '1.18.2639' of framework, I am getting Access Violation when using DynArrayLoadJSON for a specific record definition.

Record definition which gets access violation:

  TStTaxRate = packed record
    taxRate: string;
  end;
  TStTaxRates = Array of TStTaxRate;

Below is called at FormCreate:

TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TStTaxRate), 'taxRate word');

Button click raises exception:

procedure TForm5.Button3Click(Sender: TObject);
var
  s: RawUTF8;
  stTaxRates: TStTaxRates;
begin
  SetLength(stTaxRates, 8);
  s := '[{"taxRate":100},{"taxRate":800},{"taxRate":0},{"taxRate":1800},{"taxRate":0},{"taxRate":100},{"taxRate":1800},{"taxRate":800}]';
  DynArrayLoadJSON(stTaxRates, Pointer(s), TypeInfo(TStTaxRates));
end;

I have another dynamic record which I do not have any problems with.

  TStDepartment = packed record
    szDeptName: string;
    u8TaxIndex: Byte;
    iCurrencyType: UInt16;
    iUnitType: UInt16;
    u64Limit: UInt64;
    u64Price: UInt64;
  end;
  TStDepartments = Array of TStDepartment;

  // No TTextWriter.RegisterCustomJSONSerializerFromText() call for this record since it works out of the box

procedure TForm5.Button4Click(Sender: TObject);
var
  s: RawUTF8;
  stDept: TStDepartments;
begin
  SetLength(stDept, 12);
  s := '[{"szDeptName":"KSM1","u8TaxIndex":0,"iCurrencyType":949,"iUnitType":1,"u64Limit":99999999,"u64Price":0},{"szDeptName":"KSM2","u8TaxIndex":1,"iCurrencyType":949,"iUnitType":1,"u64Limit":99999999,"u64Price":0},{"szDeptName":"KSM3","u8TaxIndex":2,"iCurrencyType":949,"iUnitType":1,"u64Limit":99999999,"u64Price":0},{"szDeptName":"KSM4","u8TaxIndex":3,"iCurrencyType":949,"iUnitType":1,"u64Limit":99999999,"u64Price":0},{"szDeptName":"KSM5","u8TaxIndex":4,"iCurrencyType":949,"iUnitType":1,"u64Limit":99999999,"u64Price":0},{"szDeptName":"KSM6","u8TaxIndex":5,"iCurrencyType":949,"iUnitType":1,"u64Limit":99999999,"u64Price":0},{"szDeptName":"KSM7","u8TaxIndex":6,"iCurrencyType":949,"iUnitType":1,"u64Limit":99999999,"u64Price":0},{"szDeptName":"KSM8","u8TaxIndex":7,"iCurrencyType":949,"iUnitType":1,"u64Limit":99999999,"u64Price":0},{"szDeptName":"KSM9","u8TaxIndex":0,"iCurrencyType":949,"iUnitType":1,"u64Limit":99999999,"u64Price":0},{"szDeptName":"KSM10","u8TaxIndex":1,"iCurrencyType":949,"iUnitType":1,"u64Limit":99999999,"u64Price":0},{"szDeptName":"KSM11","u8TaxIndex":2,"iCurrencyType":949,"iUnitType":1,"u64Limit":99999999,"u64Price":0},{"szDeptName":"KSM12","u8TaxIndex":3,"iCurrencyType":949,"iUnitType":1,"u64Limit":99999999,"u64Price":0}]';
  DynArrayLoadJSON(stDept, Pointer(s), TypeInfo(TStDepartments));
end;

Offline

#18 2016-05-13 13:19:13

AOG
Member
Registered: 2014-02-24
Posts: 490

Re: Json serialization of a class

Try:

initialization
  TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TStTaxRate), 'taxRate string');
end.

So, replace "word" with "string" .....

Offline

#19 2016-05-13 14:38:10

ertank
Member
Registered: 2016-03-16
Posts: 168

Re: Json serialization of a class

That did not work (function returned nil). Most likely because JSON formatting is a number formatting.

Last edited by ertank (2016-05-13 14:45:40)

Offline

#20 2016-05-18 09:34:05

ertank
Member
Registered: 2016-03-16
Posts: 168

Re: Json serialization of a class

I could solve my problem as below:

  TStTaxRateString = packed record
    taxRate: string;
  end;
  TStTaxRatesString = Array of TStTaxRateString;

  TStTaxRateInteger = packed record
    taxRate: UInt32;
  end;
  TStTaxRatesInteger = Array of TStTaxRateInteger;


procedure TfrmMain.btnTaxClick(Sender: TObject);
var
  Json: AnsiString;
  sRaw:RawUTF8;
  RetValTax, RetValDept: TBytes;
  RetCode: UInt32;
  i, NumberOfTotalTaxRates, NumberOfTotalRecordsReceived: Integer;
  Taxes: TStTaxRatesString;
  StTaxRates: TStTaxRatesInteger;
begin
  SetLength(StTaxRates, 8);
  TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TStTaxRatesString), 'taxRate word');

  // Serialize
  Json := AnsiString(DynArraySaveJSON(Taxes, TypeInfo(TStTaxRatesString)));
  SetLength(RetValTax, STANDART_BUFFER);
  RetCode := Json_FiscalPrinter_GetTaxRates(NumberOfTotalTaxRates, NumberOfTotalRecordsReceived, Json, @RetValTax[0], Length(RetValTax), 8);
  if RetCode = 0 then
  begin
    // Convert TBytes to String which is actually a Json text
    s := TEncoding.Default.GetString(RetValTax);
    // Manually add " characters at beginning of numbers
    sRaw := StringReplaceAll(RawUTF8(s), '":', '":"');
    // Manually add " characters at the end of numbers
    sRaw := StringReplaceAll(sRaw, '}', '"}');
    // Set correct formatting for de-serialization
    TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TStTaxRatesString), 'taxRate string');
    // De-serialization itself
    DynArrayLoadJSON(Taxes, Pointer(sRaw), TypeInfo(TStTaxRatesString));

    // Copy String Record Type into Integer record type with necessary convertion
    for I := 0 to 7 do
      StTaxRates[i].taxRate := StrToUInt64(Taxes[i].taxRate);    
  end;
end;

Offline

Board footer

Powered by FluxBB