维基副本镜像

这是 pascal.chat 维基的镜像。编辑、历史记录和登录都在那里。

Streaming JSON/zh CN

Deutsch · English · Polski · Русский · 中文 · 中文(中国)

JSON (JavaScript Object Notation)是一种基于文本的标准化数据格式。顾名思义,JSON 文档是有效的 JavaScript 代码,可以直接转换为 JavaScript 对象。但是,无论使用哪种编程语言,JSON 都可用于数据交换。

本教程解释了如何将 JSON 数据加载到免费的 Pascal 程序中并在那里进行处理。它还解释了如何将数据从程序转换为JSON(例如将其发送到Web浏览器)。

General requirements

加载和存储(流式处理)对象是通过 fpjsonrtti 单元完成的。使用类单元也很有意义(有关详细信息,请参见下文)。

因此,uses 语句应至少包含以下两个单元:

pascal
uses Classes, fpjsonrtti;

目前(2014 年 <> 月)Free Pascal 的流媒体系统和 JSON 之间存在一些差异:

示例程序与Free Pascal Compiler源代码一起提供在packages/fcl-json/examples目录中。

以下示例中使用了此JSON结构

pascal
{
      "id"     : 123,                                                // an integer
      "obj"    : { "name": "Hello world!" },                         // an object
      "coll"   : [ { "name": "Object 1" }, { "name": "Object 2" } ], // two objects in a TCollection
      "strings": [ "Hello 1", "Hello 2" ]                            // a string list
    }

在您的Free Pascal程序中,可以使用常量赋值来定义它,如下所示:

pascal
const JSON_TESTDATA =
    '{'+LineEnding+
    '  "id": 123,'+LineEnding+
    '  "obj": { "name": "Hello world!" },'+LineEnding+
    '  "coll": [ { "name": "Object 1" }, { "name": "Object 2" } ],'+LineEnding+
    '  "strings": [ "Hello 1", "Hello 2" ]'+LineEnding+
    '}';

Data Structure

数据的基础类是来自Classes单元的TPersistent,为其及所有子类创建了runtime type information (RTTI)。这些对于流处理是至关重要的。由于fpjsonrtti并未集成到流处理系统中,因此也可以使用编译器开关 {$M+} 翻译的任何其他类。

要读取的所有property 必须在类的 published 部分中声明为属性。通常,您可以直接使用read和write来引用数据字段(变量)。当然,如果您愿意,也可以使用getter和setter方法。

以下类定义由JSON结构得出:

pascal
type
      TNameObject = class(TCollectionItem) // class for the 'obj' property and TCollection 
      private
        fName: String;
      published
        property name: String read fName write fName;
      end;  
    
      TBaseObject = class(TPersistent)  // class for the entire JSON structure
      private
        fid: Integer;
        fObj: TNameObject;
        fColl: TCollection;
        fStrings: TStrings;
      public
        constructor Create;
        destructor Destroy; override;
      published                         // all properties must be published 
        property id: Integer read fid write fid;
        property obj: TNameObject read fObj write fObj;
        property coll: TCollection read fColl;
        property strings: TStrings read fStrings;
      end;

TNameObject类是从 TCollectionItem派生的。这意味着它既可以用于obj属性,也可以用于集合中。如果这不是所期望的,那么必须在此处定义两个不同的类。

TCollection和字符串列表必须在TBaseObject类的构造函数中创建,并在析构函数中释放。

pascal
constructor TBaseObject.Create;
    begin
      // Create Collection and StringList
      fColl    := TCollection.Create(TNameObject);
      fStrings := TStringList.Create;
      fObj     := TNameObject.Create(nil);
    end;
    
    destructor TBaseObject.Destroy;
    begin
      // Release Collection and StringList
      fColl.Free;
      fStrings.Free;
      fObj.Free;
      inherited Destroy;
    end;

如果您不希望在数据类中添加更多功能,那么它们的定义现在已经完成。

Load JSON

使用TJSONDeStreamer类中的方法Procedure JSONToObject(Const JSON : TJSONStringType; AObject : TObject);,您可以将JSON数据直接分配给“现有”对象。在调用此方法之前,您必须创建TJSONDeStreamer和目标对象。

以下方法从对象o中的JSON结构JSON_TESTDATA加载数据,然后将属性的当前值输出到控制台。

pascal
procedure DeStreamTest;
    var
      DeStreamer: TJSONDeStreamer;
      o: TBaseObject;
      no: TNameObject;
      s: String;
    begin
      WriteLn('DeStream test');
      WriteLn('======================================');
    
      // Create the DeStreamer object and target object
      DeStreamer := TJSONDeStreamer.Create(nil);
      o := TBaseObject.Create;
      try
        // Load JSON data into object o
        DeStreamer.JSONToObject(JSON_TESTDATA, o);
        // output ID
        WriteLn(o.id);
        // output object name
        WriteLn(o.obj.name); 
        // output the names of all objects
        for TCollectionItem(no) in o.coll do
          Writeln(no.name);
        // output all strings
        for s in o.strings do
          WriteLn(s);
    
      // Cleanup
      finally
        o.Destroy;
        DeStreamer.Destroy;
      end;
    end;

Saving JSON

TJSONStreamer类用于将对象转换为JSON文本。这里使用了方法Function ObjectToJSONString(AObject : TObject) : TJSONStringType;。

在以下过程中,将创建一个对象,填充测试数据,然后将其转换为JSON。JSON文本将输出到控制台。无法指定属性输出的顺序。

pascal
procedure StreamTest;
    var
      Streamer: TJSONStreamer;
      o: TBaseObject;
      JSONString: String;
    begin
      WriteLn('Stream test');
      WriteLn('======================================');
    
      Streamer := TJSONStreamer.Create(nil);
      o := TBaseObject.Create;
      try
        // Setup data
        o.id := 123;
        o.obj.name := 'Hello world!';
        TNameObject(o.coll.Add).name := 'Object 1';
        TNameObject(o.coll.Add).name := 'Object 2';
        o.strings.Add('Hello 1');
        o.strings.Add('Hello 2');
    
        Streamer.Options := Streamer.Options + [jsoTStringsAsArray]; // Save strings as JSON array
        // convert to JSON and output to console
        JSONString := Streamer.ObjectToJSONString(o);
        WriteLn(JSONString);
    
      // Cleanup
      finally
        o.Destroy;
        Streamer.Destroy;
      end;
    end;

既然我们能够以JSON格式进行保存和加载,我们也可以自定义属性的保存方式。默认情况下,TColor(TGraphicsColor)被保存为数字,但例如,我们希望将其保存为字符串,这样更便于人们阅读。

此示例使用了BGRABitmap库,该库具有一个可以轻松从字符串转换为颜色的函数。在这种情况下,流控制是来自BGRAControls包的按钮控件。

这自定义了TColor的保存和加载方式。

Saving code:

pascal
procedure TForm1.Button1Click(Sender: TObject);
    var
      Streamer: TJSONStreamer;
      JSONString: String;
    begin
      Streamer := TJSONStreamer.Create(nil);
      try
        Streamer.OnStreamProperty:=@OnStreamProperty;
        JSONString := Streamer.ObjectToJSONString(BCButton1.StateNormal);
        Memo1.Lines.Text := JSONString;
      finally
        Streamer.Destroy;
      end;
    end;
pascal
procedure TForm1.OnStreamProperty(Sender: TObject; AObject: TObject;
      Info: PPropInfo; var Res: TJSONData);
    var
      bgracolor: TBGRAPixel;
    begin
      if (Info^.PropType^.Name = 'TGraphicsColor') then
      begin
        bgracolor := ColorToBGRA(TColor(GetPropValue(AObject, Info, False)));
        Res.Free;
        Res := TJSONString.Create('rgb('+IntToStr(bgracolor.red)+','+IntToStr(bgracolor.green)+','+IntToStr(bgracolor.blue)+')');
      end;
    end;

Loading code:

pascal
procedure TForm1.Button2Click(Sender: TObject);
    var
      DeStreamer: TJSONDeStreamer;
      s: String;
    begin
      DeStreamer := TJSONDeStreamer.Create(nil);
      try
        DeStreamer.OnRestoreProperty:=@OnRestoreProperty;
        DeStreamer.JSONToObject(Memo1.Lines.Text, BCButton1.StateNormal);
      finally
        DeStreamer.Destroy;
      end;
    end;
pascal
procedure TForm1.OnRestoreProperty(Sender: TObject; AObject: TObject;
      Info: PPropInfo; AValue: TJSONData; var Handled: Boolean);
    var
      bgracolor: TBGRAPixel;
    begin
      Handled := False;
      if (Info^.PropType^.Name = 'TGraphicsColor') then
      begin
        Handled := True;
        bgracolor := StrToBGRA(AValue.AsString);
        SetPropValue(AObject, Info, BGRAToColor(bgracolor));
      end;
    end;

Conclusion

借助所介绍的知识,简单和复杂的JSON数据结构均可加载到Free Pascal程序中。如果需要对JSON数据进行任何预处理或后处理,可以先利用TJSONParser类从jsonparser单元将文本数据加载到JSON数据结构中,然后再使用fpJSON单元按需进行处理。

See Also

Notes & References

  1. http://lists.freepascal.org/fpc-pascal/2013-January/036254.html
  1. http://lists.lazarus.freepascal.org/pipermail/lazarus/2011-January/058878.html

From the Free Pascal wiki, page "Streaming JSON/zh CN", as the Internet Archive kept it on 2025-02-17, last changed on 2024-09-14. Copyright stays with the author who wrote it, according to the old wiki's Licensing page. If you have concerns about this material, message the admin.

版本3,最后由WikiArchive于修改。