Streaming JSON
note: This article was originally translated from Streaming_JSON/de German on 20141002 using Google Translat.
JSON (JavaScript Object Notation) is a text-based, standardized data format. As the name implies, JSON documents are valid JavaScript code where they can be directly converted into JavaScript objects. But in and of itself it can be used regardless of the programming language used for data exchange.
This tutorial explains how to load JSON data in a Free Pascal program to process it. It also explains how to convert FPC data into JSON (e.g. to send it to a web server).
General and conditions
Loading and storing (streaming) FPC object data is done with the fpjsonrtti unit. Furthermore, the Classes unit is useful (see below for details).
The uses statement should therefore contain at least these two units:
uses Classes, fpjsonrtti;
Currently (May 2014) there are some differences to the streaming system of Free Pascal with ??presumably JavaScript??:
- JSON data is case-sensitive. Therefore, the properties of Free Pascal objects as JSON properties must be written with correct casing.
- No properties can be defined with DefineProperties. Neither can references to methods (event handlers) be stored. 2
- TCollection and TStrings could be used as a substitute for arrays.
Demo programs are provided with the source code of Free Pascal Compiler (in the packages/fcl-json/examples directory).
As an example, we will start with the following JSON data:
{
"id" : 123, // an Integer
"obj" : { "name": "Hello world!" }, // an Object
"coll" : [ { "name": "Object 1" }, { "name": "Object 2" } ], // two objects in a TCollection
"strings": [ "Hallo 1", "Hello 2" ] // a string list
}
This can be assigned in FreePascal using a constant assignment as follows:
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
As the base class for the data itself provides TPersistent on from the Classes unit, since for them and all subclasses Laufzeit-Typinformationen (RTTI) to be created. These are absolutely required for streaming. Since fpjsonrtti not integrated into the streaming system, any other class that the compiler switch {$M+}
was translated, are used.
All properties must be read as a property in published section of the class are declared. In general, (the variable) may here with read and write directly to a data field will refer. If you want to, of course getter and setter methods can be used.
From the JSON structure results in the following class definition.
type
TNameObject = class(TCollectionItem) // class for the property 'obj' and the TCollection
private
fName: String;
published
property name: String read fName write fName;
end;
TBaseObject = class(TPersistent) // class for the JSON structure
private
fid: Integer;
fObj: TNameObject;
fColl: TCollection;
fStrings: TStrings;
public
constructor Create;
destructor Destroy; override;
published // all properties have 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;
The class TNameObject
is of TCollectionItem derived. So they can both for the property obj be used as well in the collection. If this is not desired, here two different classes must be defined.
In the constructor of the class TBaseObject the TCollection and the string list must be created and released in the destructor.
constructor TBaseObject.Create;
begin
// Collection and StringList constructor
fColl := TCollection.Create(TNameObject);
fStrings := TStringList.Create;
fObj := TNameObject.Create(nil);
end;
destructor TBaseObject.Destroy;
begin
// Collection and StringList destructor
fColl.Free;
fStrings.Free;
fObj.Free;
inherited Destroy;
end;
If you do not want any more functionality in the data classes, their definition is already done.
Load JSON
With the method Procedure JSONToObject(Const JSON : TJSONStringType; AObject : TObject);
in the TJSONDeStreamer class you can assign JSON data directly to an existing object. Before you call the method, you must create TJSONDeStreamer and the target object.
The following method loads the data from the JSON structure JSON_TESTDATA
in the o object and then writes the current values of the properties on the console.
procedure DeStreamTest;
var
DeStreamer: TJSONDeStreamer;
o: TBaseObject;
no: TNameObject;
s: String;
begin
WriteLn('DeStream test');
WriteLn('======================================');
// DeStreamer object and target object create
DeStreamer := TJSONDeStreamer.Create(nil);
o := TBaseObject.Create;
try
// Load JSON data in the object o
DeStreamer.JSONToObject(JSON_TESTDATA, o);
// ID
WriteLn(o.id);
// Object Name
WriteLn(o.obj.name);
// Print 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;
JSON store
To convert an object into a JSON text, the class TJSONStreamer is used. Here is the method Function ObjectToJSONString(AObject : TObject) : TJSONStringType;
used.
In the following procedure, an object is created, filled with the test data, and then transferred to JSON. The JSON text is printed to the console. It can not be defined, the order in which the properties are to spend
procedure StreamTest;
var
Streamer: TJSONStreamer;
o: TBaseObject;
JSONString: String;
begin
WriteLn('Stream test');
WriteLn('======================================');
Streamer := TJSONStreamer.Create(nil);
o := TBaseObject.Create;
try
// Data set
o.id := 123;
o.obj.name := 'Hallo Welt!';
TNameObject(o.coll.Add).name := 'Objekt 1';
TNameObject(o.coll.Add).name := 'Objekt 2';
o.strings.Add('Hallo 1');
o.strings.Add('Hallo 2');
Streamer.Options := Streamer.Options + [jsoTStringsAsArray]; // Strings als JSON-Array ausgeben
// JSON convert and output
JSONString := Streamer.ObjectToJSONString(o);
WriteLn(JSONString);
// Cleanup
finally
o.Destroy;
Streamer.Destroy;
end;
end;
Outlook
With the presented knowledge simple and complex JSON data structures in Free Pascal can be loaded. Should any preparatory or finishing the JSON data be necessary, the text data can initially with the classTJSONParser from the Unit jsonparser be loaded into a JSON data structure and then with the Unit fpJSON be manipulated.
Use the Options property of the class TJSONStreamer can be controlled, such as the issue's own data structures are represented in JSON.