2022/08/12

Creating Modern JSON Configuration Files Using Delphi

English

Recently, EMBT published an interesting article in their official news: "This Is How To Store Cross Platform App Settings In JSON" by the Softacom Information team. This emerging team collects and publishes articles from Delphi enthusiasts worldwide, so it's unclear who the original author is.

The article discusses that in addition to XML (Extensible Markup Language) and INI (Initial) formats, modern cross-platform applications often use JSON for configuration files, with Visual Studio Code as a classic example.

Visual Studio Code Workspace Settings File

The article pointed out that Delphi provides two JSON libraries: JSON Objects Framework and Readers and Writers JSON Framework. The author also mentioned the most widely used alternative open-source option, X-SuperObject, provided by Turkish engineer Onur YILDIZ. It's interesting to note that the JSON Objects Framework provides JSON Marshaling/Un-Marshaling, so what makes it different from X-SuperObject?

Delphi developers are spoiled for choice when it comes to using JSON. Realy?

The author complains that Delphi engineers have been spoiled by the official JSON frameworks. Is that true?

The article points out that Delphi provides two JSON libraries: JSON Objects Framework and Readers and Writers JSON Framework. As an alternative, the author suggests using the widely popular open-source variant X-SuperObject by Turkish engineer Onur YILDIZ. Curiously, JSON Objects Framework provides JSON Marshaling/Un-Marshaling, so how does it differ from X-SuperObject?


Example using JSON Objects Framework

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
TContact = class(TObject)
  public
    Name: string;
    Age: Integer;
    function ToString: string; override;
  end;
 
procedure TForm1.JSONMarshalClick(Sender: TObject);
var
  LContact: TContact;
  oMarshaller: TJSONMarshal;
  crtVal: TJSONValue;
begin
  LContact := TContact.Create; //our custom class
  LContact.Name:='Hello Eden';
  LContact.Age := 20; //fill with some data
  oMarshaller := TJSONMarshal.Create(TJSONConverter.Create); //our engine
  crtVal := oMarshaller.Marshal(LContact); //serialize to JSON
  try
    Memo1.Text := crtVal.ToString; //display
  finally //cleanup
    FreeAndNil(LContact);
    FreeAndNil(oMarshaller);
    crtVal.Free;
  end;
end;

Example execution result using JSON Objects Framework

1
2
3
4
5
6
7
8
{
  "type": "Unit2.TContact",
  "id": 1,
  "fields": {
    "Name": "Hello Eden",
    "Age": 20
  }
}

Example execution result using JSON Objects Framework

The JSON format produced by TObject using the JSON Objects Framework (JOF) is not as intuitive as the one produced by X-SuperObject. Users might need some time to learn the JOF format to manage their configurations.

This has to do with the original purpose of JOF. Around 2009, Delphi 2009 made a transition in DataSnap Framework from XML RPC to JSON RPC. As mentioned earlier, JOF was initially created to serve DataSnap, among other things, such as handling Delphi TObject's strong typing. Therefore, the format produced by JOF is not a weakly-typed compatible JSON format.

Why doesn't JOF produce widely accepted JSON formats?

So, the author's claim that Delphi engineers are spoiled by the two built-in JSON frameworks is not baseless. After getting used to Delphi's native tools, it may be difficult to accept content that deviates from the "Delphi standard."

Creating your desired format using JOF

Knowing that the built-in JOF won't produce a generic JSON format, you can still create your own. Have you heard of TDBXJSONTools? It can be used to convert TDataSet to JSON, so let's add TObject-to-JSON functionality to it!

TDBXJSONToolsHelper adds ObjToJSON functionality

The original example is excellent and deserves a 5-star rating. I modified the example into a VCL project and replaced X-SuperObject usage with JOF, such as in the configuration file loading section:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
procedure TMyProgSettings.LoadFromFile(AFileName: string = '');
var
  LJObj: TJSONObject;
begin
  if AFileName = '' then
    AFileName := GetDefaultSettingsFilename();
 
  if not FileExists(AFileName) then
    Exit;
 
  LJObj := TJSONObject.Create;
  try
    if LJObj.Parse(TFile.ReadAllBytes(AFileName), 0) > 0 then
    begin
      // Magic method from Eden's TDBXJSONToolsHelper unit
      TDBXJSONTools.JsonToObj(LJObj, Self);  
    end;
  finally
    LJObj.Free;
  end;
end;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
procedure TMyProgSettings.SaveToFile(AFileName: string = '');
var
  Json: string;
begin
  if AFileName = '' then
    AFileName := GetDefaultSettingsFilename();
 
  // Magic method from Eden's TDBXJSONToolsHelper unit
  with TDBXJSONTools.ObjToJSON(Self) do
  begin
    Json := ToJson;
    TFile.WriteAllText(AFileName, Json, TEncoding.UTF8);
    Free;
  end;
end;

Conclusion

Being "spoiled" by JOF is, in my opinion, a blessing. JOF is a framework well worth learning, and X-SuperObject offers even more features for JSON writing. If you frequently need to write JSON objects, X-SuperObject will definitely win your heart. Additionally, I enjoyed the original author's example of a login process using a while loop to control the login window; it's a great concept to learn.

You can download the TDBXJSONToolsHelper and VCL example program from my Github website: https://github.com/Eden5Wu/HelperClass/tree/master/Demo-JSON/JsonMarshalling

In summary, both the JSON Objects Framework (JOF) and X-SuperObject have their own unique advantages when working with JSON in Delphi. Developers should choose between them based on their specific needs and preferences.

For developers familiar with the Delphi style, JOF provides a familiar environment for working with JSON. However, if you need more control over JSON object writing, then X-SuperObject may be a better choice.

In any case, exploring and learning about the different JSON frameworks available is essential. By understanding their strengths and weaknesses, you can make more informed decisions about which tools to use in your projects.

Remember, practice and experimentation are key to becoming proficient in any programming language or framework. Therefore, don't be afraid to try different approaches and learn from the examples and experiences of other developers in the Delphi community.

中文版:使用 Delphi 寫出現代化 JSON 格式的設定檔案

最近 EMBT 官方新聞出現我很感興趣的文章:This Is How To Store Cross Platform App Settings In JSON,是由 Softacom Information 團隊執筆,這是一個新興團隊,匯整來自全球各地 Delphi 愛好者的文章並發表,所以還不知道真實原作者是誰。

原文作者說明在跨平台應用程式在讀寫設定檔時,除了 XML (Extensible Markup Language) 和 INI (Initial) 外,還有一個現代化應用程式常使用的設定檔格式:JSON,例如 Visual Studio Code 就是一個經典的參考。

Visual Studio Code 工作區設定檔

Delphi developers are spoiled for choice when it comes to using JSON. Realy?

原作控訴 Delphi 工程師已經被官方 JSON 框架寵壞了 (笑),是真的嗎?

文章中指出 Delphi 提供 JSON 兩個函式庫:JSON Objects Framework 和 Readers and Writers JSON Framework;而原作設計上使用最廣泛的替代開源變體 —— 土耳其工程師 Onur YILDIZ 提供的 X-SuperObject。好奇的是,JSON Objects Framework 有提供 JSON Marshaling / Un-Marshaling,這和 X-SuperObject 又有什麼不一樣嗎?

使用 JSON Objects Framework 編寫的範例

    TContact = class(TObject)
      public
        Name: string;
        Age: Integer;
        function ToString: string; override;
      end;
     
    procedure TForm1.JSONMarshalClick(Sender: TObject);
    var
      LContact: TContact;
      oMarshaller: TJSONMarshal;
      crtVal: TJSONValue;
    begin
      LContact := TContact.Create; //our custom class
      LContact.Name:='Hello Eden';
      LContact.Age := 20; //fill with some data
      oMarshaller := TJSONMarshal.Create(TJSONConverter.Create); //our engine
      crtVal := oMarshaller.Marshal(LContact); //serialize to JSON
      try
        Memo1.Text := crtVal.ToString; //display
      finally //cleanup
        FreeAndNil(LContact);
        FreeAndNil(oMarshaller);
        crtVal.Free;
      end;
    end;

使用 JSON Objects Framework 編寫範例的執行結果

    {
      "type": "Unit2.TContact",
      "id": 1,
      "fields": {
        "Name": "Hello Eden",
        "Age": 20
      }
    }

比較 X-SuperObject 和 JSON Objects Framework

TObject 透過 JSON Objects Framework (JOF) 所產出的 JSON 格式並沒有像 X-SuperObject 產出的 JSON 格式來得直觀,若使用者要自行控制設定時恐怕還得花上一點時間學習 JOF 格式才行。

為什麼 JOF 不產出廣泛被接受的 JSON 格式?

這就要講到 JOF 是為什麼被做出來的,約 2009 年時,Delphi 2009 對 DataSnap Framework 進行變革,由 XML RPC 轉向為 JSON RPC,由上可知,JOF 當初建構目標是為 DataSnap 而服務,當然這只是其中之一;例如還必須考慮到 Delphi TObject 強型別的對應等,所以 JOF 產出的格式自然不會是以弱型別相容的 JSON 格式。

所以原作指出 Delphi 工程師被內建的兩個 JSON 框架寵壞也不是沒有道理,因為習慣了 Delphi 原生工具後的確會很難接受非【Delphi 標準】的內容。

使用 JOF 拼出自己理想中的格式

已經知道內建的 JOF 不會產出通用 JSON 格式,不過轉個彎,自己來打造一個也是可以的,知道 TDBXJSONTools 嗎?TDataSet 轉 JSON 可以利用它來完成,就來為它加入 TObject 轉 JSON 功能!

TDBXJSONToolsHelper 添加 ObjToJSON 功能

原文的範例寫的真的棒,要給 5 星評價,我把範例改為 VCL 專案,並且把使用 X-SuperObject 的地方改為 JOF,例如載入設定檔案的地方:

    procedure TMyProgSettings.LoadFromFile(AFileName: string = '');
    var
      LJObj: TJSONObject;
    begin
      if AFileName = '' then
        AFileName := GetDefaultSettingsFilename();
     
      if not FileExists(AFileName) then
        Exit;
     
      LJObj := TJSONObject.Create;
      try
        if LJObj.Parse(TFile.ReadAllBytes(AFileName), 0) > 0 then
        begin
          TDBXJSONTools.JsonToObj(LJObj, Self);  // Magic method from Eden's TDBXJSONToolsHelper unit
        end;
      finally
        LJObj.Free;
      end;
    end;

以及儲存設定檔案的地方:

    procedure TMyProgSettings.SaveToFile(AFileName: string = '');
    var
      Json: string;
    begin
      if AFileName = '' then
        AFileName := GetDefaultSettingsFilename();
     
      // Magic method from Eden's TDBXJSONToolsHelper unit
      with TDBXJSONTools.ObjToJSON(Self) do
      begin
        Json := ToJson;
        TFile.WriteAllText(AFileName, Json, TEncoding.UTF8);
        Free;
      end;
    end;

總結

能被 JOF 寵壞我個人是覺得是幸福的,JOF 是個非常值得一學的框架,而 X-SuperObject 多了更多對 JSON 寫入的功能,如果有經常對 JSON 物件寫入的需求,則 X-SuperObject 一定深得你心;另外也很喜歡原作範例中登入流程的程式內容,使用 while 迴圈控制登入視窗很有意思,要學起來。

 你可以從我的 Github 網站下載 TDBXJSONToolsHelper 和 VCL 範例程式:https://github.com/Eden5Wu/HelperClass/tree/master/Demo-JSON/JsonMarshalling


See also

 

 

 

 

 

 

沒有留言:

張貼留言