Sign in Join
Wiki copytemporary

Streaming JSON: what changed

Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11[[JSON]] (JavaScript Object Notation) is a text-based, standardized data format. As the name implies, JSON documents are valid JavaScript code and can be directly converted into JavaScript objects. However, JSON can be used for data exchange regardless of the programming language used.
22
33This tutorial explains how to load JSON data into a Free Pascal program and process it there. It also explains how to convert data from the program into JSON (eg to send it to a web browser).
44
55## General requirements
66
77Loading and storing (streaming) objects is done with the fpjsonrtti unit. It also makes sense to use the Classes unit (see below for details).
88
99The uses statement should therefore contain at least these two units:
1010
1111``` pascal
1212uses Classes, fpjsonrtti;
1313```
1414
1515Currently (May 2014) there are some differences between Free Pascal's streaming system and JSON:
1616
1717- JSON is a case-sensitive data format. It follows that the properties of the Free Pascal objects must be written in the same case as the JSON properties.
1818- No properties can be defined with DefineProperties. No references to methods (event handlers) can be saved.2
1919- [[TCollection]] and [[TStrings]] can used as a replacements for arrays.
2020
2121Demo programs are provided with Free Pascal Compiler source code in the `packages/fcl-json/examples` directory.
2222
2323This JSON structure is used in the examples which follow:
2424
2525``` JavaScript
2626{
2727 "id" : 123, // an integer
2828 "obj" : { "name": "Hello world!" }, // an object
2929 "coll" : [ { "name": "Object 1" }, { "name": "Object 2" } ], // two objects in a TCollection
3030 "strings": [ "Hello 1", "Hello 2" ] // a string list
3131}
3232```
3333
3434It can be defined in your Free Pascal program using a constant assignment as follows:
3535
3636``` pascal
3737const JSON_TESTDATA =
3838'{'+LineEnding+
3939' "id": 123,'+LineEnding+
4040' "obj": { "name": "Hello world!" },'+LineEnding+
4141' "coll": [ { "name": "Object 1" }, { "name": "Object 2" } ],'+LineEnding+
4242' "strings": [ "Hello 1", "Hello 2" ]'+LineEnding+
4343'}';
4444```
4545
4646## Data Structure
4747
4848The base class for the data is [[TPersistent]] from the Classes unit, [[Runtime Type Information (RTTI)|runtime type information (RTTI)]] is created for it and all subclasses. These are essential for streaming. Since fpjsonrtti does not integrate into the streaming system, any other class translated with the compiler switch **{\$M+}** can also be used.
4949
5050All properties to be read must be declared as a [[Property|property]] in the [[Published|published]] section of the class. As a rule, you can use read and write to refer directly to a data field (the variable). If you want, you can of course use getter and setter methods.
5151
5252The following class definition results from the JSON structure:
5353
5454``` pascal
5555type
5656 TNameObject = class(TCollectionItem) // class for the 'obj' property and TCollection
5757 private
5858 fName: String;
5959 published
6060 property name: String read fName write fName;
6161 end;
6262
6363 TBaseObject = class(TPersistent) // class for the entire JSON structure
6464 private
6565 fid: Integer;
6666 fObj: TNameObject;
6767 fColl: TCollection;
6868 fStrings: TStrings;
6969 public
7070 constructor Create;
7171 destructor Destroy; override;
7272 published // all properties must be published
7373 property id: Integer read fid write fid;
7474 property obj: TNameObject read fObj write fObj;
7575 property coll: TCollection read fColl;
7676 property strings: TStrings read fStrings;
7777 end;
7878```
7979
8080The `TNameObject` class was derived from [[TCollectionItem]]. This means that it can be used both for the obj property and in the collection. If this is not desired, then two different classes must be defined here.
8181
8282The TCollection and the string list must be created in the constructor of the TBaseObject class and released in the destructor.
8383
8484``` pascal
8585constructor TBaseObject.Create;
8686begin
8787 // Create Collection and StringList
8888 fColl := TCollection.Create(TNameObject);
8989 fStrings := TStringList.Create;
9090 fObj := TNameObject.Create(nil);
9191end;
9292
9393destructor TBaseObject.Destroy;
9494begin
9595 // Release Collection and StringList
9696 fColl.Free;
9797 fStrings.Free;
9898 fObj.Free;
9999 inherited Destroy;
100100end;
101101```
102102
103103If you do not want any more functionality in the data classes, their definition is now complete.
104104
105105## Load JSON
106106
107107With 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.
108108
109109The following method loads the data from the JSON structure `JSON_TESTDATA` in the object o and then outputs the current values ​​of the properties to the console.
110110
111111``` pascal
112112procedure DeStreamTest;
113113var
114114 DeStreamer: TJSONDeStreamer;
115115 o: TBaseObject;
116116 no: TNameObject;
117117 s: String;
118118begin
119119 WriteLn('DeStream test');
120120 WriteLn('======================================');
121121
122122 // Create the DeStreamer object and target object
123123 DeStreamer := TJSONDeStreamer.Create(nil);
124124 o := TBaseObject.Create;
125125 try
126126 // Load JSON data into object o
127127 DeStreamer.JSONToObject(JSON_TESTDATA, o);
128128 // output ID
129129 WriteLn(o.id);
130130 // output object name
131131 WriteLn(o.obj.name);
132132 // output the names of all objects
133133 for TCollectionItem(no) in o.coll do
134134 Writeln(no.name);
135135 // output all strings
136136 for s in o.strings do
137137 WriteLn(s);
138138
139139 // Cleanup
140140 finally
141141 o.Destroy;
142142 DeStreamer.Destroy;
143143 end;
144144end;
145145```
146146
147147## Saving JSON
148148
149149The class [[TJSONStreamer]] is used to convert an object into JSON text. Here the method `Function ObjectToJSONString(AObject : TObject) : TJSONStringType;` is used.
150150
151151In the following procedure, an object is created, filled with the test data, and then converted to JSON. The JSON text is output on the console. The order in which the properties are output cannot be specified.
152152
153153``` pascal
154154procedure StreamTest;
155155var
156156 Streamer: TJSONStreamer;
157157 o: TBaseObject;
158158 JSONString: String;
159159begin
160160 WriteLn('Stream test');
161161 WriteLn('======================================');
162162
163163 Streamer := TJSONStreamer.Create(nil);
164164 o := TBaseObject.Create;
165165 try
166166 // Setup data
167167 o.id := 123;
168168 o.obj.name := 'Hello world!';
169169 TNameObject(o.coll.Add).name := 'Object 1';
170170 TNameObject(o.coll.Add).name := 'Object 2';
171171 o.strings.Add('Hello 1');
172172 o.strings.Add('Hello 2');
173173
174174 Streamer.Options := Streamer.Options + [jsoTStringsAsArray]; // Save strings as JSON array
175175 // convert to JSON and output to console
176176 JSONString := Streamer.ObjectToJSONString(o);
177177 WriteLn(JSONString);
178178
179179 // Cleanup
180180 finally
181181 o.Destroy;
182182 Streamer.Destroy;
183183 end;
184184end;
185185```
186186
187187## Custom properties
188188
189189Now that we can save and load to JSON, we can as well customize how the property is saved. By default a TColor (TGraphicsColor) is saved as a number, but for example we want to make it a string, so is more human readable.
190190
191191This example uses BGRABitmap library, that has a function to convert from string to color easily. The streamed control in this case is a button control from BGRAControls package.
192192
193193This customizes how TColor is saved and loaded.
194194
195195Saving code:
196196
197197``` pascal
198198procedure TForm1.Button1Click(Sender: TObject);
199199var
200200 Streamer: TJSONStreamer;
201201 JSONString: String;
202202begin
203203 Streamer := TJSONStreamer.Create(nil);
204204 try
205205 Streamer.OnStreamProperty:=@OnStreamProperty;
206206 JSONString := Streamer.ObjectToJSONString(BCButton1.StateNormal);
207207 Memo1.Lines.Text := JSONString;
208208 finally
209209 Streamer.Destroy;
210210 end;
211211end;
212212```
213213
214214``` pascal
215215procedure TForm1.OnStreamProperty(Sender: TObject; AObject: TObject;
216216 Info: PPropInfo; var Res: TJSONData);
217217var
218218 bgracolor: TBGRAPixel;
219219begin
220220 if (Info^.PropType^.Name = 'TGraphicsColor') then
221221 begin
222222 bgracolor := ColorToBGRA(TColor(GetPropValue(AObject, Info, False)));
223223 Res.Free;
224224 Res := TJSONString.Create('rgb('+IntToStr(bgracolor.red)+','+IntToStr(bgracolor.green)+','+IntToStr(bgracolor.blue)+')');
225225 end;
226226end;
227227```
228228
229229Loading code:
230230
231231``` pascal
232232procedure TForm1.Button2Click(Sender: TObject);
233233var
234234 DeStreamer: TJSONDeStreamer;
235235 s: String;
236236begin
237237 DeStreamer := TJSONDeStreamer.Create(nil);
238238 try
239239 DeStreamer.OnRestoreProperty:=@OnRestoreProperty;
240240 DeStreamer.JSONToObject(Memo1.Lines.Text, BCButton1.StateNormal);
241241 finally
242242 DeStreamer.Destroy;
243243 end;
244244end;
245245```
246246
247247``` pascal
248248procedure TForm1.OnRestoreProperty(Sender: TObject; AObject: TObject;
249249 Info: PPropInfo; AValue: TJSONData; var Handled: Boolean);
250250var
251251 bgracolor: TBGRAPixel;
252252begin
253253 Handled := False;
254254 if (Info^.PropType^.Name = 'TGraphicsColor') then
255255 begin
256256 Handled := True;
257257 bgracolor := StrToBGRA(AValue.AsString);
258258 SetPropValue(AObject, Info, BGRAToColor(bgracolor));
259259 end;
260260end;
261261```
262262
263263## Conclusion
264264
265265With the knowledge presented, simple and complex JSON data structures can be loaded into Free Pascal programs. Should any pre- or post-processing of the JSON data be necessary, the text data can be first loaded from the [[jsonparser]] unit into a JSON data structure using the [[TJSONParser]] class and then manipulated as desired with the [[fpJSON]] unit.
266266
267267The Options property of the TJSONStreamer class can be used to control how the output maps its own data structures in JSON.
268268
269269## See Also
270270
271271- [[JSON]]
272272- [[fcl-json]]
273273- [[Streaming components]]
274274
275275## Notes & References
276276
277-1. <div id="ref1">
278-
279- <http://lists.freepascal.org/fpc-pascal/2013-January/036254.html>
280-
281- </div>
277+1. <http://lists.freepascal.org/fpc-pascal/2013-January/036254.html>
282278
2832792. \<div id="ref2\>http://lists.lazarus.freepascal.org/pipermail/lazarus/2011-January/058878.html
284- </div>
285280
286281---
287282*From the Free Pascal wiki, page "Streaming JSON", as it stood on 2022-08-06 after a change by Kai Burghardt, out of the wiki's dump of 2024-05-26. 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](/wiki-contact).*

Back to history