mORMot and Open Source friends
Check-in [64320a3a98]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:implement woHumanReadableEnumSetAsComment option for JSON serialization (and corresponding TEnumType.GetEnumNameTrimedAll() method) - also includes regresion tests for all human readable JSON format features
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 64320a3a9870c647f4e224c81a24f903b142922c
User & Date: User 2013-11-10 13:03:04
Context
2013-11-10
13:15
enhanced PostgreSQL support via ZDBC, and fix error when using schema at index creation check-in: bbe911d7d2 user: User tags: trunk
13:03
implement woHumanReadableEnumSetAsComment option for JSON serialization (and corresponding TEnumType.GetEnumNameTrimedAll() method) - also includes regresion tests for all human readable JSON format features check-in: 64320a3a98 user: User tags: trunk
2013-11-09
22:43
TSQLDBStatement.FetchAllToJSON will now add column names (in non-expanded JSON format) if no data row is returned - just like TSQLRequest.Execute check-in: 44ef602e2c user: User tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to SQLite3/mORMot.pas.

760
761
762
763
764
765
766


767
768
769
770
771
772
773
....
1737
1738
1739
1740
1741
1742
1743


1744
1745
1746
1747
1748
1749
1750
.....
18786
18787
18788
18789
18790
18791
18792



















18793
18794
18795
18796
18797
18798
18799
.....
29858
29859
29860
29861
29862
29863
29864

29865
29866
29867
29868





29869
29870
29871
29872
29873
29874
29875
.....
30015
30016
30017
30018
30019
30020
30021
30022

30023
30024
30025


30026
30027

30028
30029
30030
30031
30032
30033
30034
30035
30036
.....
30038
30039
30040
30041
30042
30043
30044


30045
30046
30047
30048
30049
30050
30051
    - added TAuthSession.SentHeaders, RemoteIP and ConnectionID properties
    - added process of Variant and WideString types in TSQLRecord properties
    - added JSON serialization of Variant and WideString types in JSONToObject()
      / ObjectToJSON() functions and WriteObject method
    - added TTypeInfo.ClassCreate() method to create a TObject instance from RTTI
    - TEnumType.GetEnumNameValue() will now recognize both 'sllWarning' and
      'Warning' text as a sllWarning item (will enhance JSONToObject() process)


    - added ClassInstanceCreate() function calling any known virtual constructor
    - added TPropInfo.ClassFromJSON() to properly unserialize TObject properties
    - added TSQLPropInfo.SQLFieldTypeName property
    - fixed [f96cf0fc5d] and [221ee9c767] about TSQLRecordMany JSON serialization
    - fixed issue when retrieving a TSQLRecord containing TSQLRecordMany
      properties with external tables (like 'no such column DestList' error)
      via SQLite3 virtual tables (e.g. for a JOINed query like FillPrepareMany)
................................................................................
    function GetCaptionStrings(UsedValuesBits: Pointer=nil): string;
    /// add caption names, ready to be display, to a TStrings class
    // - add pointer(ord(element)) as Objects[] value
    // - if UsedValuesBits is not nil, only the corresponding bits set are added
    // - can be used e.g. to populate a combo box as such:
    // ! PTypeInfo(TypeInfo(TMyEnum))^.EnumBaseType^.AddCaptionStrings(ComboBox.Items);
    procedure AddCaptionStrings(Strings: TStrings; UsedValuesBits: Pointer=nil);


    /// get the corresponding enumeration ordinal value, from its name without
    // its first lowercase chars ('Done' will find otDone e.g.)
    // - return -1 if not found (don't use directly this value to avoid any GPF)
    function GetEnumNameTrimedValue(const EnumName: ShortString): Integer; overload;
    /// get the corresponding enumeration ordinal value, from its name without
    // its first lowercase chars ('Done' will find otDone e.g.)
    // - return -1 if not found (don't use directly this value to avoid any GPF)
................................................................................
{$endif}

function TEnumType.GetCaption(const Value): string;
// GetCaptionFromPCharLen() expect ASCIIz -> use temp RawUTF8
begin
  GetCaptionFromPCharLen(pointer(GetEnumNameTrimed(Value)),result);
end;




















procedure TEnumType.AddCaptionStrings(Strings: TStrings; UsedValuesBits: Pointer=nil);
var i, L: integer;
    Line: array[byte] of AnsiChar;
    P: PAnsiChar;
    V: PShortString;
    s: string;
................................................................................
begin
  fInternalJSONWriter.Free;
  inherited;
end;

procedure TJSONSerializer.WriteObject(Value: TObject; Options: TTextWriterWriteObjectOptions);
var Added: boolean;

procedure HR(P: PPropInfo=nil);
var i: integer;
begin
  if woHumanReadable in Options then begin





    AddCR;
    for i := 1 to fHumanReadableLevel do
      Add(#9);
  end;
  if P=nil then
    exit;
  AddPropName(P^.Name);
................................................................................
          if (V<>P^.Default) or not (woDontStoreDefault in Options) then begin
            HR(P);
            if {$ifdef FPC}(Kind=tkBool){$else}
               (Kind=tkEnumeration) and (P^.PropType^=TypeInfo(boolean)){$endif} then
              AddString(JSON_BOOLEAN[boolean(V)]) else
              if (woFullExpand in Options) or (woHumanReadable in Options) then
              case Kind of
              tkEnumeration: begin

                 Add('"');
                 AddTrimLeftLowerCase(P^.PropType^^.EnumBaseType^.GetEnumNameOrd(V));
                 Add('"');


              end;
              tkSet: begin

                Add('[');
                with P^.PropType^^.SetEnumType^ do
                if (woHumanReadableFullSetsAsStar in Options) and
                   (MaxValue<32) and GetAllBits(V,MaxValue+1) then
                  AddShort('"*"') else begin
                  PS := @NameList;
                  for j := MinValue to MaxValue do begin
                    if GetBit(V,j) then begin
                      Add('"');
................................................................................
                      Add('"',',');
                    end;
                    inc(PtrUInt(PS),ord(PS^[0])+1); // next item
                  end;
                end;
                CancelLastComma;
                Add(']');


              end;
              else
                Add(V);
              end else
                Add(V); // typecast enums and sets as plain integer by default
          end;
        end;






>
>







 







>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>




>
>
>
>
>







 







|
>

|

>
>

|
>

<







 







>
>







760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
....
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
.....
18790
18791
18792
18793
18794
18795
18796
18797
18798
18799
18800
18801
18802
18803
18804
18805
18806
18807
18808
18809
18810
18811
18812
18813
18814
18815
18816
18817
18818
18819
18820
18821
18822
.....
29881
29882
29883
29884
29885
29886
29887
29888
29889
29890
29891
29892
29893
29894
29895
29896
29897
29898
29899
29900
29901
29902
29903
29904
.....
30044
30045
30046
30047
30048
30049
30050
30051
30052
30053
30054
30055
30056
30057
30058
30059
30060
30061

30062
30063
30064
30065
30066
30067
30068
.....
30070
30071
30072
30073
30074
30075
30076
30077
30078
30079
30080
30081
30082
30083
30084
30085
    - added TAuthSession.SentHeaders, RemoteIP and ConnectionID properties
    - added process of Variant and WideString types in TSQLRecord properties
    - added JSON serialization of Variant and WideString types in JSONToObject()
      / ObjectToJSON() functions and WriteObject method
    - added TTypeInfo.ClassCreate() method to create a TObject instance from RTTI
    - TEnumType.GetEnumNameValue() will now recognize both 'sllWarning' and
      'Warning' text as a sllWarning item (will enhance JSONToObject() process)
    - implement woHumanReadableFullSetsAsStar and woHumanReadableEnumSetAsComment
      option for JSON serialization and TEnumType.GetEnumNameTrimedAll()
    - added ClassInstanceCreate() function calling any known virtual constructor
    - added TPropInfo.ClassFromJSON() to properly unserialize TObject properties
    - added TSQLPropInfo.SQLFieldTypeName property
    - fixed [f96cf0fc5d] and [221ee9c767] about TSQLRecordMany JSON serialization
    - fixed issue when retrieving a TSQLRecord containing TSQLRecordMany
      properties with external tables (like 'no such column DestList' error)
      via SQLite3 virtual tables (e.g. for a JOINed query like FillPrepareMany)
................................................................................
    function GetCaptionStrings(UsedValuesBits: Pointer=nil): string;
    /// add caption names, ready to be display, to a TStrings class
    // - add pointer(ord(element)) as Objects[] value
    // - if UsedValuesBits is not nil, only the corresponding bits set are added
    // - can be used e.g. to populate a combo box as such:
    // ! PTypeInfo(TypeInfo(TMyEnum))^.EnumBaseType^.AddCaptionStrings(ComboBox.Items);
    procedure AddCaptionStrings(Strings: TStrings; UsedValuesBits: Pointer=nil);
    /// retrieve all trimed element names as CSV
    procedure GetEnumNameTrimedAll(var result: RawUTF8);
    /// get the corresponding enumeration ordinal value, from its name without
    // its first lowercase chars ('Done' will find otDone e.g.)
    // - return -1 if not found (don't use directly this value to avoid any GPF)
    function GetEnumNameTrimedValue(const EnumName: ShortString): Integer; overload;
    /// get the corresponding enumeration ordinal value, from its name without
    // its first lowercase chars ('Done' will find otDone e.g.)
    // - return -1 if not found (don't use directly this value to avoid any GPF)
................................................................................
{$endif}

function TEnumType.GetCaption(const Value): string;
// GetCaptionFromPCharLen() expect ASCIIz -> use temp RawUTF8
begin
  GetCaptionFromPCharLen(pointer(GetEnumNameTrimed(Value)),result);
end;

procedure TEnumType.GetEnumNameTrimedAll(var result: RawUTF8);
var i: integer;
    V: PShortString;
begin
  with TTextWriter.CreateOwnedStream(1024) do
  try
    V := @NameList;
    for i := MinValue to MaxValue do begin
      AddTrimLeftLowerCase(V);
      Add(',');
      inc(PtrUInt(V),length(V^)+1);
    end;
    CancelLastComma;
    SetText(result);
  finally
    Free;
  end;
end;

procedure TEnumType.AddCaptionStrings(Strings: TStrings; UsedValuesBits: Pointer=nil);
var i, L: integer;
    Line: array[byte] of AnsiChar;
    P: PAnsiChar;
    V: PShortString;
    s: string;
................................................................................
begin
  fInternalJSONWriter.Free;
  inherited;
end;

procedure TJSONSerializer.WriteObject(Value: TObject; Options: TTextWriterWriteObjectOptions);
var Added: boolean;
    CustomComment: RawUTF8;
procedure HR(P: PPropInfo=nil);
var i: integer;
begin
  if woHumanReadable in Options then begin
    if CustomComment<>'' then begin
      AddShort(' // ');
      AddString(CustomComment);
      CustomComment := '';
    end;
    AddCR;
    for i := 1 to fHumanReadableLevel do
      Add(#9);
  end;
  if P=nil then
    exit;
  AddPropName(P^.Name);
................................................................................
          if (V<>P^.Default) or not (woDontStoreDefault in Options) then begin
            HR(P);
            if {$ifdef FPC}(Kind=tkBool){$else}
               (Kind=tkEnumeration) and (P^.PropType^=TypeInfo(boolean)){$endif} then
              AddString(JSON_BOOLEAN[boolean(V)]) else
              if (woFullExpand in Options) or (woHumanReadable in Options) then
              case Kind of
              tkEnumeration:
              with P^.PropType^^.EnumBaseType^ do begin
                 Add('"');
                 AddTrimLeftLowerCase(GetEnumNameOrd(V));
                 Add('"');
                 if woHumanReadableEnumSetAsComment in Options then
                   GetEnumNameTrimedAll(CustomComment);
              end;
              tkSet:
              with P^.PropType^^.SetEnumType^ do begin
                Add('[');

                if (woHumanReadableFullSetsAsStar in Options) and
                   (MaxValue<32) and GetAllBits(V,MaxValue+1) then
                  AddShort('"*"') else begin
                  PS := @NameList;
                  for j := MinValue to MaxValue do begin
                    if GetBit(V,j) then begin
                      Add('"');
................................................................................
                      Add('"',',');
                    end;
                    inc(PtrUInt(PS),ord(PS^[0])+1); // next item
                  end;
                end;
                CancelLastComma;
                Add(']');
               if woHumanReadableEnumSetAsComment in Options then
                 GetEnumNameTrimedAll(CustomComment);
              end;
              else
                Add(V);
              end else
                Add(V); // typecast enums and sets as plain integer by default
          end;
        end;

Changes to SynCommons.pas.

3823
3824
3825
3826
3827
3828
3829



3830
3831
3832
3833
3834
3835
3836
3837
3838
3839
  // avoid serializing properties including a default value (JSONToObject function
  // will set the default values, so it may help saving some bandwidth or storage)
  // - woFullExpand will generate a log-friendly layout, including instance class
  // name and reference pointer (it is used in TSynLog)
  // - woStoreClassName will add a "ClassName":"TMyClass", field
  // - woHumanReadableFullSetsAsStar will store an human-readable set with
  // all its enumerates items set to be stored as ["*"]



  TTextWriterWriteObjectOption = (
    woHumanReadable, woDontStoreDefault, woFullExpand, woStoreClassName,
    woHumanReadableFullSetsAsStar);
  /// options set for TTextWriter.WriteObject() method
  TTextWriterWriteObjectOptions = set of TTextWriterWriteObjectOption;

  /// simple writer to a Stream, specialized for the TEXT format
  // - use an internal buffer, faster than string+string
  // - some dedicated methods is able to encode any data with JSON escape
  TTextWriter = class






>
>
>


|







3823
3824
3825
3826
3827
3828
3829
3830
3831
3832
3833
3834
3835
3836
3837
3838
3839
3840
3841
3842
  // avoid serializing properties including a default value (JSONToObject function
  // will set the default values, so it may help saving some bandwidth or storage)
  // - woFullExpand will generate a log-friendly layout, including instance class
  // name and reference pointer (it is used in TSynLog)
  // - woStoreClassName will add a "ClassName":"TMyClass", field
  // - woHumanReadableFullSetsAsStar will store an human-readable set with
  // all its enumerates items set to be stored as ["*"]
  // - woHumanReadableEnumSetAsComment will add a comment at the end of the
  // line, containing all available values of the enumaration or set, e.g:
  // $ "Enum": "Destroying", // Idle,Started,Finished,Destroying
  TTextWriterWriteObjectOption = (
    woHumanReadable, woDontStoreDefault, woFullExpand, woStoreClassName,
    woHumanReadableFullSetsAsStar, woHumanReadableEnumSetAsComment);
  /// options set for TTextWriter.WriteObject() method
  TTextWriterWriteObjectOptions = set of TTextWriterWriteObjectOption;

  /// simple writer to a Stream, specialized for the TEXT format
  // - use an internal buffer, faster than string+string
  // - some dedicated methods is able to encode any data with JSON escape
  TTextWriter = class

Changes to SynSelfTests.pas.

3135
3136
3137
3138
3139
3140
3141











3142
3143
3144
3145
3146
3147
3148
....
3286
3287
3288
3289
3290
3291
3292


3293
3294
3295
3296
3297
3298
3299
....
3418
3419
3420
3421
3422
3423
3424




















































3425
3426
3427
3428
3429
3430
3431
  fColl := Value;
end;

{$endif FPC}
{$endif DELPHI5OROLDER}
{$endif LVCL}













procedure TTestLowLevelTypes.EncodeDecodeJSON;
var J,U: RawUTF8;
    V: TPUtf8CharDynArray;
    i, a, err: integer;
    r: Double;
{$ifndef DELPHI5OROLDER}
................................................................................
{$endif}
{$endif}
{$endif}

{$ifndef LVCL}
{$ifndef DELPHI5OROLDER}
var P: PUTF8Char;


{$endif}
{$endif}
{$ifdef USEVARIANTS}
var Va: Variant;
    c: currency;
{$endif}
begin
................................................................................
  J := GetJSONObjectAsSQL(J,false,true);
  Check(J=U);
  J := '{"RowID":  210 ,"Name":"Alice","Role":"User","Last Login":null, // comment'#13#10+
    '"First Login" : /* to be ignored */  null  ,  "Department"  :  "{\"relPath\":\"317\\\\\",\"revision\":1}" } ]';
  RemoveCommentsFromJSON(@J[1]);
  J := GetJSONObjectAsSQL(J,false,true,1,True);
  Check(J=U);




















































{$ifndef LVCL}
  C2 := TCollTst.Create;
  Coll := TCollTst.Create;
  try
     U := ObjectToJSON(Coll);
     Check(Hash32(U)=$95B54414);
     Check(ObjectToJSON(C2)=U);






>
>
>
>
>
>
>
>
>
>
>







 







>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
....
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
....
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
  fColl := Value;
end;

{$endif FPC}
{$endif DELPHI5OROLDER}
{$endif LVCL}

type
  TPersistentToJSON = class(TPersistent)
  protected
    fName: RawUTF8;
    fEnum: TSynBackgroundThreadProcessStep;
    fSets: TSynBackgroundThreadProcessSteps;
  published
    property Name: RawUTF8 read fName;
    property Enum: TSynBackgroundThreadProcessStep read fEnum default flagIdle;
    property Sets: TSynBackgroundThreadProcessSteps read fSets default [];
  end;

procedure TTestLowLevelTypes.EncodeDecodeJSON;
var J,U: RawUTF8;
    V: TPUtf8CharDynArray;
    i, a, err: integer;
    r: Double;
{$ifndef DELPHI5OROLDER}
................................................................................
{$endif}
{$endif}
{$endif}

{$ifndef LVCL}
{$ifndef DELPHI5OROLDER}
var P: PUTF8Char;
    O,O2: TPersistentToJSON;
    E: TSynBackgroundThreadProcessStep;
{$endif}
{$endif}
{$ifdef USEVARIANTS}
var Va: Variant;
    c: currency;
{$endif}
begin
................................................................................
  J := GetJSONObjectAsSQL(J,false,true);
  Check(J=U);
  J := '{"RowID":  210 ,"Name":"Alice","Role":"User","Last Login":null, // comment'#13#10+
    '"First Login" : /* to be ignored */  null  ,  "Department"  :  "{\"relPath\":\"317\\\\\",\"revision\":1}" } ]';
  RemoveCommentsFromJSON(@J[1]);
  J := GetJSONObjectAsSQL(J,false,true,1,True);
  Check(J=U);
  O := TPersistentToJSON.Create;
  O2 := TPersistentToJSON.Create;
  try
    J := ObjectToJSON(O,[]);
    Check(J='{"Name":"","Enum":0,"Sets":0}');
    J := ObjectToJSON(O,[woDontStoreDefault]);
    Check(J='{"Name":""}');
    J := ObjectToJSON(O,[woStoreClassName]);
    Check(J='{"ClassName":"TPersistentToJSON","Name":"","Enum":0,"Sets":0}');
    J := ObjectToJSON(O,[woHumanReadable]);
    Check(J='{'#$D#$A#9'"Name": "",'#$D#$A#9'"Enum": "Idle",'#$D#$A#9'"Sets": []'#$D#$A'}');
    with PTypeInfo(TypeInfo(TSynBackgroundThreadProcessStep))^.EnumBaseType^ do
    for E := low(E) to high(E) do begin
      O.fName := Int32ToUTF8(ord(E));
      O.fEnum := E;
      include(O.fSets,E);
      J := ObjectToJSON(O,[]);
      Check(J=FormatUTF8('{"Name":"%","Enum":%,"Sets":%}',[ord(E),ord(E),byte(O.fSets)]));
      JSONToObject(O2,pointer(J),valid);
      Check(Valid);
      Check(O.Name=O2.Name);
      Check(O.Enum=O2.Enum);
      Check(O.Sets=O2.Sets);
      J := ObjectToJSON(O,[woHumanReadable]);
      U := FormatUTF8(
        '{'#$D#$A#9'"NAME": "%",'#$D#$A#9'"ENUM": "%",'#$D#$A#9'"SETS": ["IDLE"',
        [ord(E),UpperCaseU(GetEnumNameTrimed(E))]);
      Check(IdemPChar(pointer(J),pointer(U)));
      JSONToObject(O2,pointer(J),valid);
      Check(Valid);
      Check(O.Name=O2.Name);
      Check(O.Enum=O2.Enum);
      Check(O.Sets=O2.Sets);
    end;
    J := ObjectToJSON(O,[woHumanReadable,woHumanReadableFullSetsAsStar]);
    Check(J='{'#$D#$A#9'"Name": "3",'#$D#$A#9'"Enum": "Destroying",'#$D#$A#9'"Sets": ["*"]'#$D#$A'}');
    J := ObjectToJSON(O,[woHumanReadable,woHumanReadableFullSetsAsStar,woHumanReadableEnumSetAsComment]);
    Check(J='{'#$D#$A#9'"Name": "3",'#$D#$A#9'"Enum": "Destroying", // Idle,Started,Finished,Destroying'+
      #$D#$A#9'"Sets": ["*"] // Idle,Started,Finished,Destroying'#$D#$A'}');
    O2.fName := '';
    O2.fEnum := low(E);
    O2.fSets := [];
    RemoveCommentsFromJSON(@J[1]);
    JSONToObject(O2,pointer(J),valid);
    Check(Valid);
    Check(O.Name=O2.Name);
    Check(O.Enum=O2.Enum);
    Check(O.Sets=O2.Sets);
  finally
    O2.Free;
    O.Free;
  end;
{$ifndef LVCL}
  C2 := TCollTst.Create;
  Coll := TCollTst.Create;
  try
     U := ObjectToJSON(Coll);
     Check(Hash32(U)=$95B54414);
     Check(ObjectToJSON(C2)=U);