Currently, XML and JSON formats have emerged as the primary standards for data storage and information exchange between computer systems. At the request of users, classes for handling XML and JSON have been added to FastScript. In this article, we’ll take a closer look at how to work with these classes, explore their properties and methods, and also create reports from code using scripts.
To work with XML in scripts, 2 classes are used. These classes are focused on maximum speed and low memory consumption.
TfrXMLDocument – a class that encapsulates the functionality of an XML document. The following properties and methods are available in it.
Class Properties and Methods | Description |
procedure SaveToStream(Stream: TStream); |
Saves the XML to the passed stream. |
procedure LoadFromStream(Stream: TStream; AllowPartialLoading: Boolean = False); |
Loads the XML from the passed stream. |
procedure SaveToFile(const FileName: string); |
Saves the XML to a file with the specified name. |
procedure LoadFromFile(const FileName: string); |
Loads the XML from a file with the specified name. |
procedure Clear; |
Deletes all XML nodes from the tree, except the root node. The content of the node is NOT cleared. |
property Root: TfrXMLItem; |
Allows access to the root element of the tree. |
property AsText: string; |
Allows you to both get and set the XML as a string (for example, using this property, you can pass the XML to the report script using a report variable). |
property AutoIndent: Boolean; |
Determines how the output XML will be generated: as a single line or as indented text. |
TfrXMLNode – a class that encapsulates the properties of an XML document node.
You cannot create an object of this type directly. It is created by calling the Add() method of the element to which you want to add a child element. Let’s take a closer look at the properties and methods of the TfrXMLNode class.
Class Properties and Methods | Description |
function Add(AName:string):TfrXMLItem; |
Creates a child TfrXMLItem with the specified name and returns it. |
procedure Clear; |
Clears the list of child elements. |
procedure InsertItem(Index:integer; AItem: TfrXMLItem); |
Inserts a child element into the specified position. The element may belong to another document. |
function Find(AName:string):Integer; |
Searches the child elements for a TfrXMLItem with the specified name and returns it. If the element is not found, -1 is returned. If there are several elements with the specified name, the first one is returned. |
function FindItem(AName:string):TfrXMLItem; |
Searches the child elements for a TfrXMLItem with the specified name and returns it. If the element is not found, a new element with the specified name is created and returned. If there are several elements with the specified name, the first one is returned. |
function Root: TfrXMLItem; |
Returns the root element of the document. |
property Count:Integer; |
Returns the number of child nodes of the element. |
property Items[AIndex:Integer]: TfrXMLItem; |
Returns a child element by its index. |
property Prop[AName:string]:string; |
Returns or sets the value of the node’s attribute with the specified name. |
property Name: string; |
The tag name of the element. |
property Parent: TfrXMLItem; |
The name of the parent element. Root is nil. |
property Text:string; |
A string containing a list of node parameters in the format Name1="Value1" Name2="Value2"… Special characters in this string are quoted. |
procedure Delete(AIndex: Integer); |
Deletes a child element with AIndex. |
procedure DeleteProp(const APropName: string); |
Deletes the node’s attribute with the specified name. |
property Value: string; |
The text that is located between the opening and closing tag of the element. |
function PropExists(APropName:string):Boolean; |
Helps to determine whether an element has an attribute with the specified name. |
function IndexOf(AItem: TfrXMLItem):Integer; |
Returns the index of the passed element. |
function GetPropNames(ANames:TStrings); |
Fills the passed list of strings with the names of the TfrXMLItem attributes. |
Let’s try to build a report using classes for working with XML. For example, we will display data from the "country.xml" file in the report, but we will not use TClientDataSet, and will use direct access to the XML file. We create a new report, go to the editing mode, put MasterData on the report. And add several Memo on top to display the data. We will also create an OnBeforePrint event for the MasterData object.
The final report script code will look like this:
var doc: TfrXMLDocument; item: TfrXMLItem; IIndex: Integer; procedure MasterData1OnBeforePrint(Sender: TfrxComponent); var cur: TfrXMLItem; S: string; begin cur := item[IIndex]; mmNum.Text := cur.Prop['Code']; mmName.Text := cur.Prop['Name']; mmCapital.Text := cur.Prop['Capital']; mmArea.Text := cur.Prop['Area']; mmPopulation.Text := cur.Prop['Population']; mmContinent.Text := cur.Prop['Continent']; Inc(IIndex); end; begin Doc := TfrXMLDocument.Create; Doc.LoadFromFile('..\..\Data\country.xml'); item := Doc.Root[1]; IIndex := 0; MasterData1.RowCount := item.count; end.
Let’s go ahead and run the report. To make it easier to read, we’ve also added column headers.
Let’s smoothly transition from XML to JSON format. To work with it in FastReport, 2 classes are also used—TfrJSON and TfrJSONArray. In this case, these classes are simply a wrapper around classes from System.JSON in the Delphi platform or similar classes in Lazarus.
TfrJSONObject — a class that encapsulates functions for working with JSON objects.
Class Properties and Methods | Description |
function Create(const JSONstring: string); |
Creates a JSON object and populates it from the provided string. |
function IsValid:Boolean; |
Returns True if the object contains valid JSON. |
procedure LodFromFile(const AFilName:string); |
Loads data from the specified file. If the object contains data, it will be lost. |
procedure LoadFromtStream(const AStream:TStream); |
Loads data from the provided stream. If the object contains data, it will be lost. |
function ToString:string; |
Returns a string containing the textual representation of the JSON object. |
procedure SaveToFile(const AFileName: string); |
Saves the textual representation of the JSON object to a file. |
procedure SaveToStream(AStream: TStream |
Saves the textual representation of the JSON object to a stream. |
function IsNameExists(const AName:string); |
Returns True if the object has a property with the specified name. |
function IsNameValueExists(const Name, Value: string): boolean; |
Returns True if the object has a property with the specified name and its value matches the specified value. |
function Count:Integer; |
Returns the total number of properties in the JSON object. |
property Names[AIndex:Integer]:string; |
Returns the name of the property at the specified index. |
Property ValueType[IndexOrName:Variant]:TfrJSONType; |
Returns the type of the property at the specified index or name. The type can be one of the following values: - jsUnknown–—The type of the property is unknown (a parsing error occurred or a property with such an index or name is missing). - jsNumber—The property has a numeric type. - jsString—The property has a string type. - jsBoolean—The property has a boolean type (true/false). - jsNull—The type of the property is null (no value is assigned). - jsList—The type of the property is a JSON array. - jsObject—The type of the property is a nested JSON object. |
The properties below allow you to retrieve the values of the JSON object in one form or another. In all these properties, indexing is performed either by the property number in the list of properties or by its name. Note that AsString[‘1’] <> AsString[1]. Also, if you try to get the value of a property using an inappropriate method, errors may occur and incorrect property values may be returned. For example, if a property has the type "Object" or "Array", then when you try to get its value using the AsString property, you will get an empty string. In other cases, AsString will return the string representation of the property’s value.
Class Properties and Methods | Description |
property AsObject[IndexOrName: Variant]: TfrxJSON; |
Allows you to retrieve a nested JSON object. |
property AsArray[IndexOrName: Variant]: TfrJSONArray; |
Allows you to retrieve a nested JSON array. |
Property AsString[IndexOrName: Variant]: string; |
Allows you to retrieve the value of a property as a string. This retrieval method can be applied to properties of other types, except for objects and arrays. For boolean values, a string containing the value "True" or "False" will be returned. For numbers with a fractional part, a dot will always be used as the fractional part separator, regardless of regional settings. |
property AsNumber[IndexOrName: Variant]: Extended; |
Allows you to retrieve the value of a property as a number. If the property has a non-numeric type, 0 will be returned. |
property AsBoolean[IndexOrName: Variant]: Boolean; |
Allows you to retrieve the value of a property as a boolean value. If the property has another type, False will be returned. |
To return a field of type null, you’ll have to put in a little effort. There isn’t a dedicated method for that. However, you can use the ValueType property, which returns jsNull for a field of type null, or use the AsString property, which returns the string ‘null’.
Let’s take a closer look at the methods for manipulating object properties.
Class Methods | Description |
Procedure Delete(AName: String); |
Removes a property with the specified name from the object. |
function AddObject(const AName: string): Integer |
Adds a child empty object with the specified name to the object. |
function AddArray(const AName: string): Integer; |
Adds an empty array with the specified name to the object. |
function AddString(const AName, AValue: string): Integer; |
Adds a string with the specified name to the object. |
function AddBool(const AName: string; AValue: boolean): Integer; |
Adds a boolean value with the specified name to the object. |
function AddNumber(const AName: string; AValue:Extended): Integer; |
Adds a numeric real value with the specified name to the object. |
function AddInteger(const AName: string; AValue:Integer): Integer; |
Adds a numeric integer value with the specified name to the object. |
function AddNull(const AName: string): Integer; |
Adds a null value with the specified name to the object. |
The methods return an integer, which is the index of the added element in the list of all fields.
All methods that add fields to the object do not control the presence of existing fields with the same names. This procedure is the responsibility of the programmer using this class. Such control is necessary to prevent the appearance of multiple fields with the same names in the output JSON. The behavior of the system in this case is undefined—it depends on the parser that will further parse the received JSON. This means that any of the values with the same names may be used!
The TJSONArray class encapsulates methods and properties for working with JSON arrays. In principle, the main properties are the same, but you can only access them by index. Only operations to access any element, get its value, or delete any element are supported. You cannot move elements in the array or change their type. Let’s consider the properties and methods for obtaining the values of elements in this class.
Class Properties and Methods | Description |
function Count:Integer; |
Returns the number of elements in the array. |
property ValueType[AIndex: Integer]: TfrJSONType; |
Returns the type of the array element with the specified index. |
property AsObject[AIndex: Integer]: TfrJSONObject; |
Returns an object providing access to the array element as a JSON object. |
Property AsArray[AIndex: Integer]: TfrJSONArray |
Returns an object providing access to the array element as a JSON array. |
property AsString[AIndex: Integer]: string; |
Returns the value of the array element as a string. |
property AsNumber[AIndex: Integer]: Extended; |
Returns the value of the array element as a number. |
property AsBoolean[AIndex: Integer]: Boolean; |
Returns the value of the array element as a boolean value. |
When dealing with arrays, like with objects, you need to carefully approach extracting the values of elements. Depending on the type of the value, you should use the appropriate method to retrieve it.
The methods for manipulating elements of a JSON array are described below.
Class Properties and Methods | Description |
procedure Delete(AIndex:Integer); |
Deletes an array element at the specified index. |
function AddObject: Integer; |
Adds a nested JSON object to the end of the array. |
function AddArray: Integer; |
Adds a nested JSON array to the end of the array. |
function AddString(const AValue: string): Integer; |
Adds a string to the end of the array. |
function AddBool(AValue: boolean): Integer; |
Adds a boolean value to the end of the array. |
function AddNumber(AValue:Extended): Integer; |
Adds a floating-point number to the end of the array. |
function AddInteger(AValue: Integer): Integer; |
Adds an integer to the end of the array. |
function AddNull: Integer; |
Adds null to the end of the array. |
All of these methods return the position of the element added to the array. You cannot insert an element into the middle of the array; this is a limitation of the base classes in System.JSON.
When using objects, it’s important to remember that after using wrapper objects, you must remove all of them from the code.
Note that if you want to get a child object or array, but instead there’s a scalar value or no value at all, the property will return nil, and an exception will occur when accessing that property further. Avoid using methods that are not intended to work with values of specific types stored in an element to retrieve those values.
It’s important to note that property names of objects are case-sensitive, meaning "Item1" and "item1" are considered different names!
You can only set values for object elements if the type of those values matches the type that is defined for the element. If you need to redefine the type of an element, delete it and add a new one with the same name and the type you want. Entering new values for array elements is not possible. You need to delete the JSON array and then recreate it with the values you want.
Let’s use the classes from the description above and write a script that extracts data (without type information) from an XML file and puts it into a JSON object.
procedure XMLToJSON; var XML: TfrXMLDocument; xi, xi1: TfrXMLItem; I, J: Integer; JO: TfrJSONObject; JA, JA1: TfrJSONArray; SL: TStringList; begin XML:=TfrXMLDocument.Create; XML.LoadFromFile('.\..\..\data\country.xml'); JO:=TfrJSONObject.Create('{}'); JO.AddArray('data'); JA:=JO.AsArray['data']; xi:=X.Root[1]; xi1:=xi.Items[0]; SL:=TStringList.Create; xi1.GetParamNames(SL); for I:=0 to xi.Count - 1 do begin xi1:=xi.Items[I]; JA1:=JA.AsArray[JA.AddArray]; for J:=0 to SL.Count-1 do JA1.AddString(xi1.Prop[SL[J]]); JA1.Free; end; JA.Free; JO.SaveToFile('.\..\..\data\country.json'); JO.Free; SL.Free; end;
And let’s try to build a report based on the obtained data (first, place the appropriate MasterData and Memo components on the report page):
var J:TfrJSONObject; A:TfrJSONArray; curr:Integer; procedure MasterData1OnBeforePrint(Sender: TfrxComponent); var A1:TfrJSONArray; begin A1:=A.AsArray(curr); Memo1.Memo.Text:=A1.AsString[0]; Memo2.Memo.Text:=A1.AsString[1]; Memo3.Memo.Text:=A1.AsString[2]; Memo4.Memo.Text:=A1.AsString[3]; Memo5.Memo.Text:=A1.AsString[4]; Memo6.Memo.Text:=A1.AsString[5]; A1.Free; curr := curr + 1; end; begin J:=TfrJSONObject.Create(''); J.LoadFromFile('.\..\..\data\country.json'); A:=J.AsArray[0]; MasterData1.RowCount:=A.Count; curr:=0; end.
You can also use these objects in Delphi code—simply include the frXML and frJSON modules in the uses statement.
In this article, we explored the use of classes for working with JSON and XML in FastReport. If you’re a developer working not just with reports but also with Delphi and Lazarus, utilizing these classes will help minimize issues when switching between environments—your experience will be consistent across both IDEs.
For your convenience, we’ve included two examples in this article demonstrating report generation using XML and JSON. You can open and run these examples directly in FastReportDemo, which comes with the FastReport distribution. You might need to configure the reports before running them—just make sure to specify the correct path to the data file "country.xml," which can be found both in the DemoCenter and in the folder with the examples.