Search code examples
jsonrestdelphivcldelphi-xe8

Delphi Rest.JSON JsonToObject only works with f variables


I'm using Delphi XE8.

I was just looking at the REST.Json ObjectToJsonString() and JsonToObject() calls.

Mainly attempting to do something like this:

How to convert an object to JSON and back with a single line of code

I noticed that I could only get variables to work when they started with an F character. I couldn't find any documentation on this. Is this expected behavior? Should I be naming all variables inside of my classes with an F at the start? If Yes, can someone explain why?

I created a class TTestJSON and defined two member variables and set them to 'WORKS' and 'FAILS'.

Then I created a JSON string value from the object:

{
  "varThatWorksBeacuseItStartsWithF":"WORKS",
  "sVarThatFailsBecauseItStartsWithS":"FAILS"
} 

When going back from a JSON string to object, only the fVarThatWorksBeacuseItStartsWithF variable is being reset correctly. In the code below, test := TJson.JsonToObject<TTestJSON>(JsonStr); using the above JSON, notice that the sVarThatFailsBecauseItStartsWithS is "" and not "FAILS".

procedure TForm3.btn1Click(Sender: TObject);
var
  test : TTestJSON;
  JsonStr : String;
begin
  m1.Clear;
  test := TTestJSON.Create;
  try
    test.fVarThatWorksBeacuseItStartsWithF := 'WORKS';
    test.sVarThatFailsBecauseItStartsWithS := 'FAILS';
    JsonStr := TJson.ObjectToJsonString( test );
  finally
    test.Free;
  end;
  m1.Lines.Add(  '** JSONStr Value START **' + #13#10 + JsonStr + '** JSONStr Value END **' + #13#10 );

  test := TJson.JsonToObject<TTestJSON>(JsonStr);
  try
    m1.Lines.Add('** Obj loaded from JSON String Start **' + #13#10 + TJson.ObjectToJsonString( test ) + #13#10 + '** Obj loaded from JSON String End **');
  finally
    test.Free;
  end;
end;

From the results, the var that starts with f has the f stripped out of the JSON string, and the one that starts with s still has it in there. I would have expected that the second result would have looked like this:

{
  "varThatWorksBeacuseItStartsWithF":"WORKS",
  "sVarThatFailsBecauseItStartsWithS":"FAILS"
}

Here is the full code to reproduce - just has a button and a memo on a vcl form - also uses REST.Json:

unit Main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, rest.Json;

type
  TTestJSON = class
    fVarThatWorksBeacuseItStartsWithF : String;
    sVarThatFailsBecauseItStartsWithS : String;
  end;

  TForm3 = class(TForm)
    btn1: TButton;
    m1: TMemo;
    procedure btn1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form3: TForm3;

implementation

{$R *.dfm}

procedure TForm3.btn1Click(Sender: TObject);
var
  test : TTestJSON;
  JsonStr : String;
begin
  m1.Clear;
  test := TTestJSON.Create;
  try
    test.fVarThatWorksBeacuseItStartsWithF := 'WORKS';
    test.sVarThatFailsBecauseItStartsWithS := 'FAILS';
    JsonStr := TJson.ObjectToJsonString( test );
  finally
    test.Free;
  end;
  m1.Lines.Add(  '** JSONStr Value START **' + #13#10 + JsonStr + '** JSONStr Value END **' + #13#10 );

  test := TJson.JsonToObject<TTestJSON>(JsonStr);
  try
    m1.Lines.Add('** Obj loaded from JSON String Start **' + #13#10 + TJson.ObjectToJsonString( test ) + #13#10 + '** Obj loaded from JSON String End **');
  finally
    test.Free;
  end;
end;

end.

Solution

  • JSON serialization in Delphi is based on fields, not properties. But most of Delphi classes have friendly properties and F-prefixed fields. At same time seems Emb is trying to avoid of F-prefixed names in generated JSON. They cut off first "F" from name when serialize fields and add it back (to find correct field) when read from JSON. Seems the only (safe) way to use JSON serialization in Delphi is to keep all field names with prefix "F" (for the fields that you want to serialize):

    TTestJSON = class
    protected
      FName: String;
    public
      property Name: String read FName write FName;
    end;
    

    UPDATE2: As David mentions, we can use attributes and then we have much better control:

    uses
      REST.Json.Types, // without this unit we get warning: W1025 Unsupported language feature: 'custom attribute'
      REST.Json;
    
    type
      // All fields of records are serialized, no control here.
      TRec = record
        RecStr: String;
      end;
    
      // By default all fields of class are serialized, but only F-prefixed serialized correctly.
      // We can use JSONMarshalled attribute to enable/disable serialization.
      // We can use JSonName attribute to serialize field with specific name in JSON.
      TTestJSON = class
        [JSONMarshalled(True)] [JSonName('RecField')]
        R: TRec;
      end;
    
    procedure TForm28.FormCreate(Sender: TObject);
    var
      Test: TTestJSON;
      JsonStr: string;
    begin
      Test := TTestJSON.Create;
      try
        Test.R.RecStr := 'Some str';
        JsonStr := TJson.ObjectToJsonString( Test );
      finally
        FreeAndNil(Test);
      end;
    
      // JsonStr: {"RecField":["Some str"]}
    
      Test := TJson.JsonToObject<TTestJSON>(JsonStr);
      FreeAndNil(Test);
    end;