Delphi: Accessing JSON Objects Within a JSON Array

Delphi: Accessing JSON Objects within a JSON Array

If you have an array from DBXJSON, then it is a TJSONArray. Call its Get method to get an element of the array.

var
Value: TJSONValue;

Value := jArray.Get(0);

You can also go through the entire array with a for loop:

for Value in jArray do

But if you check the Size property and get 6226004 instead of 3, that suggests there's something else wrong here. My guess is that what you think is a TJSONArray isn't really that type. Use as to do a checked type cast:

jArray := jPair.JsonValue as TJSONArray;

You'll get an EInvalidCast exception if that fails.

JSON object- how to iterate through all properties without knowing their names?

To enumerate a JSONObject, you can use an enumerator like below.

Use for..in loop (implicit enumerator)

procedure TForm1.Button1Click(Sender: TObject);
var
JSONData : String;
JSONObject : TJSONObject;
JSONPair : TJSONPair;
begin
JSONData := '... some JSON data ...'; // Place you data here
JSONObject := TJSonObject.ParseJSONValue(JSONData) as TJSONObject;
try
for JSONPair in JSONObject do
ShowMessage(Format('1) Key=%s Value=%s',
[JSONPair.JsonString.ToString,
JSONPair.JsonValue.ToString]));
finally
JSONObject.Free;
end;
end;

Use a classic for loop:

procedure TForm1.Button1Click(Sender: TObject);
var
JSONData : String;
JSONObject : TJSONObject;
JSONPair : TJSONPair;
I : Integer;
begin
JSONData := '... some JSON data ...'; // Place you data here
JSONObject := TJSonObject.ParseJSONValue(JSONData) as TJSONObject;
try
for I := 0 to JSONObject.Count - 1 do begin
ShowMessage(Format('Key=%s Value=%s',
[JSONObject.Pairs[I].JsonString.ToString,
JSONObject.Pairs[I].JsonValue.ToString]));
end;
finally
JSONObject.Free;
end;
end;

Use an explicit enumerator and a while loop:

procedure TForm1.Button1Click(Sender: TObject);
var
JSONData : String;
JSONObject : TJSONObject;
JSONEnumerator : TJSONObject.TEnumerator;
JSONPair : TJSONPair;
begin
JSONData := '... some JSON data ...'; // Place you data here
JSONObject := TJSonObject.ParseJSONValue(JSONData) as TJSONObject;
try
JSONEnumerator := JSONObject.GetEnumerator;
try
while JSONEnumerator.MoveNext do begin
JSONPair := JSONEnumerator.Current;
ShowMessage(Format('Key=%s Value=%s',
[JSONPair.JsonString.ToString,
JSONPair.JsonValue.ToString]));
end;
finally
JSONEnumerator.Free;
end;
finally
JSONObject.Free;
end;
end;

Note that you may need to check if JSONPair.JsonValue has childs and enumerate those childs with another enumerator, recursively.

Parsing JSON Array in Delphi

A nice guy from Delphipraxis DP has provided a very sophisticated solution:

     procedure TForm1.Button1Click(Sender: TObject);
var
DataBase: String;
JsonArray: TJSONArray;
ArrayElement: TJSonValue;
RowValue: TJSonValue;
RowItem: TJSonValue;
keyFeatures: TJSonValue;
FeatureItem: TJSonValue;
FeatureList: TStringlist;
Id: Integer;
Index: Integer;
begin
Memo1.Clear;
DataBase :=
'[{"total":"9","page":"9","records":"99","rows":[{"id":"62316","titleId":"47243","subject":'
+ '["000607","000607_","001727"],"keyFeatures":["AI","URL"]},{"id":"66","titleId":"47243","subject":'
+ '["000607","000607_","001727"],"keyFeatures":["KK"]}],"suggestion":"90"}]';

JsonArray := TJSonObject.ParseJSONValue(DataBase) as TJSONArray;
try
Index := 1;
for ArrayElement in JsonArray do
begin
RowValue := (ArrayElement as TJSonObject).GetValue('rows');
if RowValue is TJSONArray
then
begin
for RowItem in TJSONArray(RowValue) do
begin
RowItem.TryGetValue('id', Id);
keyFeatures := (RowItem as TJSonObject).GetValue('keyFeatures');
if keyFeatures is TJSONArray
then
begin
FeatureList := TStringlist.Create;
try
for FeatureItem in TJSONArray(keyFeatures) do
FeatureList.Add(FeatureItem.Value);
Memo1.Lines.Add(Format('%d: %d KeyFeatures: %s', [Index, Id, FeatureList.CommaText]));
finally
FeatureList.Free;
end;
end;
end;
end;
inc(Index);
end;
finally
JsonArray.Free;
end;
end;

Delphi parsing a Json with multiple array types?

lJsonArray := lJsonScenar as TJSONArray

The root of your JSON is not an array. It is an object. That objects has a single name/value pair, named forms. You need to read that, and then look for the form by name. Like this:

lJsonObj := TJSONObject.ParseJSONValue(lJsonBytes, 0) as TJSONObject;
lJsonObj := lJsonObj.GetValue('forms') as TJSONObject;
lJsonPair := lJsonObj.Get(formNameJson);
....

This program

{$APPTYPE CONSOLE}

uses
System.SysUtils, System.JSON, System.IOUtils;

procedure Main(const fileName, formName: string);
var
lJsonBytes: TBytes;
lJsonObj: TJSONObject;
lJsonArray: TJSONArray;
lJsonValue: TJSONValue;
lJsonPair: TJSONPair;
begin
lJsonBytes := TFile.ReadAllBytes(fileName);
lJsonObj := TJSONObject.ParseJSONValue(lJsonBytes, 0) as TJSONObject;
lJsonObj := lJsonObj.GetValue('forms') as TJSONObject;
lJsonArray := lJsonObj.GetValue(formName) as TJSONArray;
Writeln(fileName, ' ', formName);
for lJsonValue in lJsonArray do begin
lJsonObj := lJsonValue as TJSONObject;
for lJsonPair in lJsonObj do begin
Writeln(lJsonPair.JsonString.ToString, ': ', lJsonPair.JsonValue.ToString);
end;
end;
Writeln;
end;

begin
try
Main('C:\desktop\json.txt', 'frmLogin');
Main('C:\desktop\json.txt', 'frmHome');
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.

has this output:


C:\desktop\json.txt frmLogin
"frmLoginPg": "Se connecter - Application de gestion de PC"
"lbl_login_Title": "Application de gestion Pc"
"lbl_loginName": "Nom d'utilisateur"
"lblLanguage": "langue préférée"
"btnLogin": "Se connecter"
"btnReset_Loginfrm": "Réinitialiser"

C:\desktop\json.txt frmHome
"frmHomepg": "Accueil"
"lbladdUser_Title": "Ajouter un utilisateur"
"lblName": "prénom"
"lblEmail": "EMail"
"popmemFile": "Fichier"

Delphi parse JSON array or array

For new readers looking for these answers.

How about this function, or even simpler if you restructure the JSON data?

function getData(JsonString: String; User: String; Field: String): String;
var
JSonValue: TJSonValue;
JsonArray: TJSONArray;
ArrayElement: TJSonValue;
FoundValue: TJSonValue;
begin
Result :='';

// create TJSonObject from string
JsonValue := TJSonObject.ParseJSONValue(JsonString);

// get the array
JsonArray := JsonValue as TJSONArray;

// iterate the array
for ArrayElement in JsonArray do begin
FoundValue := ArrayElement.FindValue(User);
if FoundValue <> nil then begin
Result := ArrayElement.GetValue<string>(User + '.' + Field);
break;
end;
end;
end;

The problem with the sample JSON code above is that it uses the users' names "a" "b" as a JSON-key {key:data} for the users' data. In this way you can't use GetValue("a") in your search for data. Structuring your JSON data differently makes the search process a lot easier. I will later on give an example of this.

A way to handle the given JSON data is by using FindValue so you can check if a field with key "a" or "b" exists.

FoundValue := ArrayElement.FindValue("b");
if FoundValue <> nil then begin
Result := ArrayElement.GetValue<string>("b"+ '.' + "email");
break;

About the 'parsing a JSON array' question: After the data is loaded as a TJSonObject you can change the data into a TJSONArray and iterate the elements.

  JsonValue := TJSonObject.ParseJSONValue(JsonString);  
JsonArray := JsonValue as TJSONArray;
for ArrayElement in JsonArray do begin
...

A working example code for the given JSON data:

unit JsonArray1;

interface

uses System.JSON;

function getData2(JsonString: String; User: String; Field: String): String;
procedure Test1();

implementation

function getData2(JsonString: String; User: String; Field: String): String;
var
JSonValue: TJSonValue;
JsonArray: TJSONArray;
ArrayElement: TJSonValue;
FoundValue: TJSonValue;
begin
Result :='';

// create TJSonObject from string
JsonValue := TJSonObject.ParseJSONValue(JsonString);

// get the array
JsonArray := JsonValue as TJSONArray;

// iterate the array
for ArrayElement in JsonArray do begin
FoundValue := ArrayElement.FindValue(User);
if FoundValue <> nil then begin
Result := ArrayElement.GetValue<string>(User + '.' + Field);
break;
end;
end;
end;

procedure Test1();
var
DataBase: String;
EmailAddress : String;
Username: String;
begin
DataBase := '[ {"a" : {"username":"aaa","email":"aaa@gmail.com"}},' +
'{"b" : {"username":"bbb","email":"bbb@gmail.com"}} ]';

EmailAddress := getData2(DataBase, 'b', 'email');
Username := getData2(DataBase, 'a', 'username');

end;

end.

As already mentioned, restructuring the JSON data with proper keys makes the code to find data more simple. Because there is a 1 on 1 relation between the users' data "a":{}, "b":{} it's easy to introduce a 'user' key. Also adding a 'users' key to the array results in all data having keys.

  '{"users" : [{ "user":"a", "username":"aaa","email":"aaa@gmail.com"},' +
'{ "user":"b", "username":"bbb","email":"bbb@gmail.com"}]}';

When you iterate the users, you can now use GetValue with the new "user" key.

  if ArrayElement.GetValue<String>('user') = 'b' then begin
Result := ArrayElement.GetValue<String>('email');

By giving the array a key you can now get the array with:

JsonArray := JsonValue.GetValue<TJSONArray>('users');

A working example code for the restructured JSON data:

unit JsonArray2;

interface

uses System.JSON;

function getData2(JsonString: String; User: String; Field: String): String;
procedure Test2();

implementation

function getData2(JsonString: String; User: String; Field: String): String;
var
JSonValue: TJSonValue;
JsonArray: TJSONArray;
ArrayElement: TJSonValue;
FoundValue: TJSonValue;
begin
Result :='';

// create TJSonObject from string
JsonValue := TJSonObject.ParseJSONValue(JsonString);

// get the array
JsonArray := JsonValue.GetValue<TJSONArray>('users');

// iterate the array
for ArrayElement in JsonArray do begin
if ArrayElement.GetValue<String>('user') = User then begin
Result := ArrayElement.GetValue<String>(Field);
break;
end;
end;
end;

procedure Test2();
var
DataBase: String;
EmailAddress : String;
Username: String;
begin
DataBase := '{"users" : [{ "user":"a", "username":"aaa","email":"aaa@gmail.com"},' +
'{ "user":"b", "username":"bbb","email":"bbb@gmail.com"}]}';

EmailAddress := getData2(DataBase, 'b', 'email');
Username := getData2(DataBase, 'a', 'username');

end;

end.

Parsing a JSON string that contains an array of an array of a string of another jsonstring in Delphi

Found these functions in system.JSON and they just clicked for me.

/// <summary>Finds a JSON value and returns reference to it. </summary>
/// <remarks> Raises an exception when a JSON value could not be found. </remarks>
property P[const APath: string]: TJSONValue read GetValueP;{ default;}
property A[const AIndex: Integer]: TJSONValue read GetValueA;

var
aSuccess, aMessage : String
aJSON : TJSONObject;
begin
var aJSON:= TJSONObject.ParseJSONValue('{"result":[[],["{\"success\": \"true\", \"Message\":\"User has been deleted.\"}"]]}');
aSuccess := TJSONObject.ParseJSONValue(aJSON.P['result'].A[1].A[0].AsType<String>).P['success'].AsType<String>;
aMessage := TJSONObject.ParseJSONValue(aJSON.P['result'].A[1].A[0].AsType<String>).P['Message'].AsType<String>;
end;

Note that this needs exception handling, all of these functions will throw an exception if they fail to find the specified property.

Gets only the first value from the JSON array. What should I do?

I see a lot of problems in this code.

In Button1Click(), the outer for loop is unnecessary and should be removed. Between the two for loops, you are re-looping through the same array over and over needlessly. The inner for..in loop will suffice.

Also, it doesn't make sense to have getValue() take the original JSON as a string, as Button1Click() has already parsed it out, just to have getValue() reparse it again again and again. Lots of wasted overhead. You should pass the existing TJSONArray or even the ArrayElement to getValue() (or better, just eliminate getValue() completely and move its logic into Button1Click directly, especially since your call to FindValue(User) doesn't make sense as User is always blank).

Also, getData() and Button1Click() are both leaking the TJSONValue objects that ParseJSONValue() returns.

Also, you should be using the overload of TIdHTTP.Get() that returns a string instead of fills a TStream.

Also, your try..finallys need some cleanup, you are not adequately protecting all of your objects from errors.

With all of that said, try something more like this instead:

unit Unit1;

interface

uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ComCtrls, IdSSL , IdSSLOpenSSL, HTTPApp , IdURI,
IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, IdHTTP, System.JSON,
Vcl.Grids, Data.DB, Vcl.DBGrids;

type
TForm1 = class(TForm)
DateTimePicker1: TDateTimePicker;
Edit1: TEdit;
Button1: TButton;
IdHTTP1: TIdHTTP;
StringGrid1: TStringGrid;
editPK: TEdit;
Memo1: TMemo;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure StringGrid1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

{Automatic column size adjustment}
procedure AutoSizeGridColumn(Grid : TStringGrid; Column : integer);
var
i : integer;
temp : integer;
max : integer;
begin
max := 0;
for i := 0 to Grid.RowCount-1 do
begin
// Among the width of each row in the specified column based on Grid Canvas
// The maximum value is determined by the width of the column
temp := Grid.Canvas.TextWidth(Grid.Cells[Column, i]);
if temp > max then
max := temp;
end;
Grid.ColWidths[Column] := max + Grid.GridLineWidth + 20;
end;
{End of autosize Column}
//*************************************************************

procedure TForm1.Button1Click(Sender: TObject);
var
idhttps: TIdHTTP;
sslIOHandler : TIdSSLIOHandlerSocketOpenSSL;
JSONValue : TJSONValue;
JSONArray : TJSONArray;
ArrayElement: TJSONValue;
i : integer;
begin
idhttps := TIdHTTP.Create;
try
sslIOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(idhttps);
sslIOHandler.SSLOptions.Mode := sslmClient;
sslIOHandler.SSLOptions.Method := sslvSSLv23;
idhttps.IOHandler := sslIOHandler;

// idhttps.Request.CustomHeaders.Add(pAuthorization);
idhttps.HandleRedirects := False;
idhttps.ConnectTimeout := 10000;
idhttps.ReadTimeout := 10000;

idhttps.HTTPOptions := idhttps.HTTPOptions + [hoNoProtocolErrorException, hoWantProtocolErrorContent];

Result := idhttps.Get('insert JSON URL');
finally
idhttps.Free;
end;

JSONValue := TJSONObject.ParseJSONValue(Result);
try
JsonArray := JSONValue as TJSONArray;
StringGrid1.RowCount := JsonArray.Count + 1;

i := 1;
for ArrayElement in JsonArray do
begin
StringGrid1.Cells[00,i] := ArrayElement.GetValue<string>('ActionName','');//Status
StringGrid1.Cells[02,i] := ArrayElement.GetValue<string>('Shipper_Name','');//Company Name
StringGrid1.Cells[03,i] := ArrayElement.GetValue<string>('LoadDate','');//Different day
StringGrid1.Cells[04,i] := ArrayElement.GetValue<string>('LoadArea','');//to take over
StringGrid1.Cells[05,i] := ArrayElement.GetValue<string>('AlightArea','');//Downloading
StringGrid1.Cells[06,i] := ArrayElement.GetValue<string>('AlightDate','');//Departure Day
StringGrid1.Cells[07,i] := ArrayElement.GetValue<string>('VehicleType','');//model
StringGrid1.Cells[08,i] := ArrayElement.GetValue<string>('VehicleWeight','');// tonnage
StringGrid1.Cells[09,i] := ArrayElement.GetValue<string>('GoodName','');//Cargo information
StringGrid1.Cells[10,i] := ArrayElement.GetValue<string>('Fee_Driver','');//vehicle freight
StringGrid1.Cells[11,i] := ArrayElement.GetValue<string>('Commission','');//commission
StringGrid1.Cells[12,i] := ArrayElement.GetValue<string>('sk','');//pk
StringGrid1.Cells[13,i] := ArrayElement.GetValue<string>('pk','');//sk

{if StringGrid1.Cells[13,i] = '300016244318#9977' then
begin
StringGrid1.Cells[01,i] := 'O'//연동화물
end else begin
StringGrid1.Cells[01,i] := '';//연동화물
end;}

Inc(i);
end;
finally
JSONValue.Free;
end;

{StringGrid1 Auto-Size Cell}
for i := 0 to StringGrid1.ColCount-1 do
begin
AutoSizeGridColumn(StringGrid1, i);
end;
{StringGrid1 Auto-Size Cell END}
end;

end.


Related Topics



Leave a reply



Submit