How to parse nested JSON object in Delphi XE2? - json

I'm new to JSON and I have this project on my hands that require me to parse a JSON and display some of its contents in a ListView. The problem is that the documentation I've read by now dealt with JSON objects containing JSON arrays, while my case involves dealing with nested objects. To cut the story short, here's the summary: I'm using Delphi XE2 with DBXJSON. I post some values to a server and it replies with a JSON object that looks like that:
{
"products": {
"Men's Sneakers": {
"instock": false,
"size": "423",
"manufacturer": "Adidas",
"lastcheck": "20120529"
},
"Purse": {
"instock": true,
"size": "not applicable",
"manufacturer": "Prada",
"lastcheck": "20120528"
},
"Men's Hood": {
"instock": false,
"size": "M",
"manufacturer": "Generic",
"lastcheck": "20120529"
}
},
"total": 41,
"available": 30
}
What I wanted to achieve was to have each item (i.e. Purse) parsed and added as caption in a listview, along with one subitem (manufacturer). I created a procedure that takes the JSON string as argument, created the JSON object, but I don't know how to parse the nested objects any further.
procedure TForm1.ParseString(const AString: string);
var
json : TJSONObject;
jPair : TJSONPair;
jValue : TJSONValue;
jcValue : TJSONValue;
l,i : Integer;
begin
json := TJSONObject.ParseJSONValue(TEncoding.ASCII.GetBytes(AString),0) as TJSONObject;
try
//get the pair to evaluate in this case the index is 1
jPair := json.Get(1);
{further process the nested objects and adding them to the listview}
finally
json.Free;
end;
end;
Any suggestions would be highly appreciated. Lost quite some time trying to get the ins and outs of JSON in Delphi with no avail.
Thanks,
sphynx

Try this sample
{$APPTYPE CONSOLE}
{$R *.res}
uses
DBXJSON,
System.SysUtils;
Const
StrJson=
'{'+
' "products": {'+
' "Men''s Sneakers": {'+
' "instock": false,'+
' "size": "423",'+
' "manufacturer": "Adidas",'+
' "lastcheck": "20120529"'+
' },'+
' "Purse": {'+
' "instock": true,'+
' "size": "not applicable",'+
' "manufacturer": "Prada",'+
' "lastcheck": "20120528"'+
' },'+
' "Men''s Hood": {'+
' "instock": false,'+
' "size": "M",'+
' "manufacturer": "Generic",'+
' "lastcheck": "20120529"'+
' }'+
' },'+
' "total": 41,'+
' "available": 30'+
'}';
procedure ParseJson;
var
LJsonObj : TJSONObject;
LJPair : TJSONPair;
LProducts : TJSONValue;
LProduct : TJSONValue;
LItem : TJSONValue;
LIndex : Integer;
LSize : Integer;
begin
LJsonObj := TJSONObject.ParseJSONValue(TEncoding.ASCII.GetBytes(StrJson),0) as TJSONObject;
try
LProducts:=LJsonObj.Get('products').JsonValue;
LSize:=TJSONArray(LProducts).Size;
for LIndex:=0 to LSize-1 do
begin
LProduct := TJSONArray(LProducts).Get(LIndex);
LJPair := TJSONPair(LProduct);
Writeln(Format('Product Name %s',[LJPair.JsonString.Value]));
for LItem in TJSONArray(LJPair.JsonValue) do
begin
if TJSONPair(LItem).JsonValue is TJSONFalse then
Writeln(Format(' %s : %s',[TJSONPair(LItem).JsonString.Value, 'false']))
else
if TJSONPair(LItem).JsonValue is TJSONTrue then
Writeln(Format(' %s : %s',[TJSONPair(LItem).JsonString.Value, 'true']))
else
Writeln(Format(' %s : %s',[TJSONPair(LItem).JsonString.Value, TJSONPair(LItem).JsonValue.Value]));
end;
end;
finally
LJsonObj.Free;
end;
end;
begin
try
ParseJson;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
This will return
Product Name Men's Sneakers
instock : false
size : 423
manufacturer : Adidas
lastcheck : 20120529
Product Name Purse
instock : true
size : not applicable
manufacturer : Prada
lastcheck : 20120528
Product Name Men's Hood
instock : false
size : M
manufacturer : Generic
lastcheck : 20120529

This site describes the type TJSONValue in more detail. If your data is an object, it will have the type TJSONObject, so check its API to see how to proceed.
I believe the first thing you need it to iterate over its pairs (use GetEnumerator if you don't know the key names, otherwise just use the overloaded Get - passing a string instead of a number). For each pair, the key will be a simple string (type TJSONString) and the value may be any TJSONValue. Repeat until you reach the leaves.
Example:
products := jPair.Get('products');
purse := products.GetJsonValue().Get('Purse');
purseManuf := purse.GetJsonValue().Get('manufacturer');
...
Or if you don't know what the products are:
products := jPair.Get('products');
for prodPair in products.GetEnumerator() do
begin
prodName := prodPair.GetJsonString();
prodObj := prodPair.GetJsonValue();
...

This blog post shows a very modern and simple way to convert JSON:
uses REST.JSON; // Also new System.JSON
procedure TForm1.Button1Click(Sender: TObject);
var
Foo: TFoo;
begin
Foo := TFoo.Create;
try
Foo.Foo := 'Hello World';
Foo.Fee := 42;
Memo1.Lines.Text := TJson.ObjectToJsonString(Foo);
finally
Foo.Free;
end;
Foo := TJson.JsonToObject<TFoo>(Memo1.Lines.Text);
try
Foo.Fee := 100;
Memo1.Lines.Add(TJson.ObjectToJsonString(Foo));
finally
Foo.Free;
end;
end;

Related

Why TFDBatchMove raises exception ELocalTimeInvalid for date field with value "03/11/2019"?

I'm using Delphi 10.3 Rio Update 1 on a Windows 7 SP1 machine.
My program's purpose is to convert a TFDMemtable into a JSON format. For a reason that I can't understand, when the date field of this TFDMemTable has the value '03/11/2019', using the DisplayFormat "day/month/year", it raises an exception:
Project ProjMemtabJSON.exe raised exception class ELocalTimeInvalid with message 'The given "03/11/2019" local time is invalid (situated within the missing period prior to DST).'.
Any other dates different than "Nov, 3rd 2019" work fine.
I have no clue what is going on here!
program ProjMemtabJSON;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
System.Classes,
System.JSON,
FireDAC.Comp.DataSet,
FireDAC.Comp.Client,
FireDAC.Comp.BatchMove,
FireDAC.Comp.BatchMove.DataSet,
FireDAC.Comp.BatchMove.JSON,
Data.DB;
Var
Fmemtable : TFDmemtable ;
FJSONArray : TJSONArray;
FDBatchMoveJSONWriter1 : TFDBatchMoveJSONWriter;
FDBatchMoveDataSetReader1 : TFDBatchMoveDataSetReader;
FDBatchMove1 : TFDBatchMove;
procedure CreateMemtable;
begin
Fmemtable := TFDmemtable.Create(nil);
FMemTable.FieldDefs.Add('ID', ftInteger, 0, False);
FMemTable.FieldDefs.Add('Name', ftString, 20, False);
FMemTable.FieldDefs.Add('Status', ftString, 20, False);
FMemTable.FieldDefs.Add('Duedate', ftdatetime,0, False);
FMemTable.CreateDataSet;
end;
procedure FillMemtable;
begin
FMemtable.Append;
FMemtable.Fields[0].Value := 10; // ID
FMemtable.Fields[1].Value := 'John'; // Name
FMemtable.Fields[2].Value := 'Active'; // Status
{ ==> HERE IS THE PROBLEM : change this date to 03/11/2019 i.e. 03/Nov/2019 and an error will raise }
FMemtable.Fields[3].Value := StrToDate('02/11/2019'); // Due date dd/mm/yyyy
end;
procedure PrintMemtable;
begin
writeln('ID : ' ,Fmemtable.Fields[0].AsString);
writeln('Name : ' ,Fmemtable.Fields[1].AsString);
writeln('Status : ' ,Fmemtable.Fields[2].AsString);
writeln('Due Date : ' ,Fmemtable.Fields[3].AsString);
end;
function TableToJson : TJSONArray;
begin
Result := TJSONArray.Create;
try
FDBatchMoveDataSetReader1 := TFDBatchMoveDataSetReader.Create(nil);
FDBatchMoveJSONWriter1 := TFDBatchMoveJSONWriter.Create(nil);
FDBatchMove1 := TFDBatchMove.Create(nil);
FDBatchMove1.Reader := FDBatchMoveDataSetReader1 ;
FDBatchMove1.Writer := FDBatchMoveJSONWriter1;
try
if not FMemtable.Active then
FMemtable.Active := True;
FDBatchMoveDataSetReader1.DataSet := FMemtable;
FDBatchMoveJSONWriter1.JsonArray := Result;
FDBatchMove1.Execute;
except
on E: Exception do
raise Exception.Create('Error Message: ' + E.Message);
end;
finally
FDBatchMoveDataSetReader1.Free;
FDBatchMoveJSONWriter1.Free;
FDBatchMove1.Free;
end;
end;
begin
try
{ TODO -oUser -cConsole Main : Insert code here }
Creatememtable;
FillMemtable;
PrintMemtable;
FJSONArray := TableToJSON;
readln;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
There is a System.DateUtils routine that can check for an invalid time:
TTimeZone.IsInvalidTime(const ADateTime : TDateTime) : Boolean;
If you add DateUtils to your Uses clause, then update your FillMemTable to something like:
procedure FillMemtable;
var MyDate : tDateTime;
begin
FMemtable.Append;
FMemtable.Fields[0].Value := 10; // ID
FMemtable.Fields[1].Value := 'John'; // Name
FMemtable.Fields[2].Value := 'Active'; // Status
{ ==> HERE IS THE PROBLEM : change this date to 03/11/2019 i.e. 03/Nov/2019 and an error will raise }
MyDate := StrToDate('03/11/2019');
if TTimeZone.local.IsInvalidTime(MyDate) then MyDate := MyDate + 0.5; //noon won't be invalid
FMemtable.Fields[3].Value := MyDate; // Due date dd/mm/yyyy
end;
Or, as mentioned in the comments, if you don't want the overhead of the IF statement, just force all dates to be noon.
I had never realized that there were time zones which switched to/from DST at midnight. Unfortunate, what with how Dates (without times) are defined.

Adding a filter expression to a JSONIterator Find function call

What is the correct syntax for a filter expression to query a JSON object with a specific attribute string value inside an array, using bracket notation?
I am limited to bracket notation because dot notation won't work in Delphi when there is a quote or apostrophe inside the filter expression.
Use [] to access object properties that do contain a quoting character
in their name. For example, use root['child.name'] or
root["child.name"] to access the child.name property of the root
object.
I've used an online JSON path evaluator against this JSON and come up with the expression result["elements"][?(#.name == 'Training Seminar - Nov 9')]. In the online evaluator, this path works fine and returns the exact object I'm looking for. However, when I run it in Delphi I get an exception that says
EJSONPathException: Invalid index for array: ?(#.name == 'Training
Seminar - Nov 9')
My question is, what is the correct syntax for a filter expression to query a JSON object with a specific attribute string value inside an array, using bracket notation?
MCVE for this as a console application, including the JSON.
program Project3;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, System.Classes, System.JSON.Builders, System.JSON.Readers, System.JSON.Types;
const JsonStr = '{' +
' "result":{ ' +
' "elements":[ ' +
' { ' +
' "id":"ML_1HMloeUjEFgKaC9",' +
' "name":"Utilization Survey",' +
' },' +
' {' +
' "id":"ML_1zQjGtGXFPkEo6N",' +
' "name":"Training Seminar - Nov 9",' +
' }' +
' ]' +
' },' +
' "meta":{' +
' "httpStatus":"200 - OK",' +
' "requestId":"ef2afd6e-3fd9-4fdf-a8fe-c935c147a0af"' +
' }' +
'}';
procedure RunIt;
var Reader : TJsonTextReader;
Iterator : TJsonIterator;
StringReader : TStringReader;
Found : Boolean;
begin
StringReader := TStringReader.Create(JsonStr);
Reader := TJsonTextReader.Create(StringReader);
Iterator := TJsonIterator.Create(Reader);
try
Found := Iterator.Find('result["elements"][?(#.name == ''Training Seminar - Nov 9'')]');
//The value of Found is false or an exception is raised because of bad syntax in the filter expression
WriteLn(BoolToStr(Found));
ReadLn;
finally
Iterator.Free;
Reader.Free;
StringReader.Free;
end;
end;
begin
try
RunIt;
except
on E: Exception do
begin
Writeln(E.ClassName, ': ', E.Message);
Readln;
end
end;
end.
You can't use expressions at all, because they are not implemented. Whether in dot notation or bracket notation. Excerpt from official documentation of System.JSON.TJSONPathParser.
These operators do not support special expressions, they only
support actual values (object properties or array indexes).
However in this case, it can be achieved in a slightly more complicated way.
procedure RunIt;
var
lJSON, lValue, lName: TJSONValue;
lFound : Boolean;
begin
lFound := False;
lJSON := TJSONObject.ParseJSONValue(JsonStr);
if Assigned(lJSON) then
try
if lJSON.TryGetValue('result.elements', lValue) and (lValue is TJSONArray) then
begin
for lValue in TJSONArray(lValue) do
begin
lFound := lValue.TryGetValue('name', lName) and (lName.Value = 'Training Seminar - Nov 9');
if lFound then
Break;
end;
end;
finally
lJSON.Free;
end;
WriteLn(BoolToStr(lFound));
ReadLn;
end;

Can't deserialize valid JSON using system.json

I write some code (rest server) that produce for me data in JSON format. When I use it in PHP it works fine, JSON is valid, everything is ok. When I use it in Delphi nothing works.
When I search internet I found:
desearilizing JSON using SuperObject
but that method returns empty strings for me.
I want to use that JSON elements as array (eg. JSONValue.items[i]).
I'm using Delphi XE7 System.JSON and don't want to use superobject or any others libraries.
How use it as array?
I paste my code that generates JSON:
var
qry: TfdQuery;
FieldsObj: TJSONObject;
FieldNameArray: TJSONArray;
I: Integer;
DataObj: TJSONObject;
DataRows: TJSONArray;
RowFields: TJSONArray;
tablename:string;
begin
tablename:='produkt';
qry := TfdQuery.Create(Self);
qry.SQL.Text := 'select * from produkt where (id ='''+ProductID+''')';
qry.Connection := FDConnection1;
qry.Open;
FieldsObj := TJSONObject.Create;
FieldNameArray := TJSONArray.Create;
for I := 0 to qry.FieldCount - 1 do
FieldNameArray.Add(qry.Fields[I].FieldName);
FieldsObj.AddPair(TableName, FieldNameArray);
DataObj := TJSONObject.Create;
DataRows := TJSONArray.Create;
qry.First;
while not qry.Eof do
begin
RowFields := TJSONArray.Create;
for I := 0 to qry.FieldCount - 1 do
RowFields.Add(qry.Fields[I].AsString);
DataRows.Add(RowFields);
qry.Next;
end;
DataObj.AddPair('data', DataRows);
Result := TJSONArray.Create(FieldsObj, DataObj);
qry.Free;
And this is the result:
{
"ProductID": "1",
"result": [{
"produkt": ["id", "parent_id", "full_name", "opcja_1", "opcja_2", "opcja_3", "opcja_4", "opcja_5", "opcja_6", "opcja_7", "opcja_8", "opcja_9", "opcja_10", "opcja_11", "opcja_12", "field_address1", "field_address2", "quantity", "opis", "zdjecie1", "zdjecie2", "zdjecie3", "samples", "link_stable0", "link_stable1", "link_stable2", "price1", "price2", "price3"]
}, {
"data": [
["1", "1", "name", "1", "1", "1", "1", "0", "0", "0", "0", "0", "0", "0", "12", "10", "20", "1,2", "description of product", "http://www.vphosted.com/e6=0", "photo link2", "photo link 3", "sample project file link", "link option", "10", "link", "10", "link", "10"]
]
}]
}
This would produce JSON more in the format that I would expect:
var
qry: TfdQuery;
FieldsObj: TJSONObject;
//FieldNameArray: TJSONArray;
I: Integer;
DataObj: TJSONObject;
FieldObj: TJSONObject;
DataRows: TJSONArray;
RowFields: TJSONArray;
tablename:string;
begin
tablename:='produkt';
qry := TfdQuery.Create(Self);
qry.SQL.Text := 'select * from produkt where (id ='''+ProductID+''')';
qry.Connection := FDConnection1;
qry.Open;
FieldsObj := TJSONObject.Create;
//FieldNameArray := TJSONArray.Create;
//for I := 0 to qry.FieldCount - 1 do
// FieldNameArray.Add(qry.Fields[I].FieldName);
//FieldsObj.AddPair(TableName, FieldNameArray);
DataObj := TJSONObject.Create;
DataRows := TJSONArray.Create;
qry.First;
while not qry.Eof do
begin
RowFields := TJSONArray.Create;
for I := 0 to qry.FieldCount - 1 do
begin
FieldObj := TJSONObject.Create;
FieldObject.AddPair(qry.Fields[I].FieldName, qry.Fields[I].AsString));
RowFields.Add( FieldObj );
end;
DataRows.Add(RowFields);
qry.Next;
end;
DataObj.AddPair('data', DataRows);
Result := TJSONArray.Create(FieldsObj, DataObj);
qry.Free;
If you know the record structure, though, I would prefer to use REST.JSON, which I am pretty sure ships with XE7 and is much simpler to use. You just create your object structure, create an instance of that structure, populate it with your field values and use
TJSON.ObjectToJsonString( fObject )
to create your string and
iObject := TJSON.JsonToObject<TMyObject>( pTransferString );
to get your object back.
If you want a more complete example using this method, let me know and I will post.

Oracle 12c does not recognize JSON type

I am working on parsing JSON CLOB in oracle. I am trying to retrieve and parse individual JSON elements.
Main issue I isolated is that compiler is unable recognize the JSON type here. However, I am able to insert and access individual JSON elements in the po_document field(declared as CLOB). This is the JSON I am trying to access
'{"PONumber" : 1600,
"Reference" : "ABULL-20140421",
"Requestor" : "Alexis Bull",
"User" : "ABULL",
"CostCenter" : "A50",
"ShippingInstructions" : "no such",
"Special Instructions" : null,
"AllowPartialShipment" : true,
"LineItems" : "no line"}'
I just took a standard Pl/SQL block to parse the JSON object:
DECLARE
vCONTENT CLOB;
v_parent_json json;
v_json_message_list json_list;
v_json_message_list_value json_value;
v_parent_json_value json_value;
BEGIN
SELECT po_document INTO vCONTENT FROM j_purchaseorder;
v_parent_json := json(vCONTENT);
v_parent_json := json(v_parent_json.get(1));
v_json_message_list := json_list(v_parent_json.get('LineItems'));
DBMS_OUTPUT.PUT_LINE(v_json_message_list.count);
for message_loop_counter in 1 ..v_json_message_list.count loop
v_parent_json_value := json(v_json_message_list.get(message_loop_counter)).get(1);
DBMS_OUTPUT.PUT_LINE(v_parent_json_value.mapname);
END LOOP;
END;
The compiler log generates the error message: Error(3,8): PLS-00201: identifier 'JSON' must be declared
Output from v$version:
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
PL/SQL Release 12.1.0.2.0 - Production
I was trying different things initially. I was using PL/JSON functions in my questions but if I want to use Oracle functions, this could be a small demo to read JSON and print values:
declare
l_has_data_level_co BOOLEAN := FALSE; -- TRUE is for R12C
jdemo CLOB;
l_name varchar2(2000):='';
l_location varchar2(2000):='';
begin
jdemo := '{"PONumber" : 1600,
"Reference" : "ABULL-20140421",
"Requestor" : "Alexis Bull",
"User" : "ABULL",
"CostCenter" : "A50",
"ShippingInstructions" : "no such",
"Special Instructions" : null,
"AllowPartialShipment" : true,
"LineItems" : "no line"}';
SELECT
json_value(jdemo, '$.PONumber'),
json_value(jdemo, '$.Reference')
into
l_name,
l_location
FROM dual;
--DBMS_OUTPUT.PUT_LINE (SYSDATE||' '||jdemo);
DBMS_OUTPUT.PUT_LINE ('Name :'||l_name||' Location :'||l_location);
end;

error converting string to JSON with RAD Studio

I have this problem :
I recive strings with this format:
{
{
"name":"j1",
"type":"12"
},
{
"name":"j2",
"type":"15"
},
.....
}
I would like to read data
like get("name") and get(type) to read all data values and show like
j1 12
j2 15
j3 23 and so on
I am using RAD studio ,firemonkey for mobile devices.
I wrote this code to parse the string to JSON an read it .
jo: TJSONObject;
jp: TJSONPair;
va1:TJSONvalue;
va2:TJSONvalue;
jo:= TJSONObject.ParseJSONValue(TEncoding.ASCII.GetBytes(a),0) as TJSONObject;
jp := jo.Get(1);
for i := 0 to jo.Size - 1 do
begin
jp := jo.Get(i);
Memo1.Lines.Add('ToString: ' + jp.ToString);
Memo1.Lines.Add('JsonString: ' + jp.JsonString.Value);
if (jp.JSONValue is TJSONTrue) or
(jp.JSONValue is TJSONFalse) or
(jp.JSONValue is TJSONNull) then
Memo1.Lines.Add('JsonValue: ' + jp.JsonValue.ToString)
else
Memo1.Lines.Add('JsonValue: ' + jp.JsonValue.Value)
end;
When the program execute jp := jo.Get(1); I have this error "Access violation at adress 53605374 accessing address 0000013C"
I have tried other ways ... but I have not been able to solve the mystery.
According to json.org this is not a valid json.
A valid json (validated online ) would be:
[
{
"name":"j1",
"type":"12"
},
{
"name":"j2",
"type":"15"
},
.....
]
Or
{
data: [
{
"name":"j1",
"type":"12"
},
{
"name":"j2",
"type":"15"
},
.....
]
}