I'm using LkJSON v1.07 to parse JSON in Delphi 7, converting it to TTreeVeiw.
I use this JSON sample from the Internet:
{
"user": {
"pk": 25025320,
"username": "instagram",
"full_name": "Instagram",
"is_private": false,
"profile_pic_id": "1360316971354486387_25025320",
"is_verified": true,
"has_anonymous_profile_picture": false,
"media_count": 5164,
"follower_count": 233901016,
"following_count": 187,
"biography": "Discovering — and telling — stories from around the world.",
"external_url": "",
"usertags_count": 144,
"hd_profile_pic_versions": [
{
"width": 320,
"height": 320,
}
],
"hd_profile_pic_url_info": {
"width": 320,
"height": 320
},
"has_highlight_reels": true,
"auto_expand_chaining": false
},
"status": "ok" ,
}
Here is the relevant Delphi code:
procedure TForm1.PopulateTree;
var
jsDoc : TlkJSONbase;
begin
jsDoc := TlkJSON.ParseText(JSONMemo.Text);
if Assigned(jsDoc) then
begin
JsonTree.Items.Clear;
DomToTree(jsDoc, nil);
end;
end;
procedure TForm1.DomToTree(JsonNode: TlkJSONbase; TreeNode: TTreeNode);
var
I,j: Integer;
NewTreeNode: TTreeNode;
NodeText: string;
jsObj: TlkJSONobject;
jsLst: TlkJSONlist;
n: integer;
begin
case JsonNode.SelfType of
jsObject : begin
jsObj := (JsonNode AS TlkJSONobject);
NodeText := jsObj.NameOf[0] + '[' + jsObj.SelfTypeName + ']';
for I := 0 to jsObj.Count - 1 do
begin
N := jsObj.FieldByIndex[i].Count;
if n > 0 then
NodeText := Format('%s [ %s ] [ %d ]', [jsObj.Nameof[i], jsObj.FieldByIndex[i].SelfTypeName , jsObj.FieldByIndex[i].Count])
else
NodeText := Format('[%s] %s = %s', [jsObj.FieldByIndex[i].SelfTypeName, jsObj.Nameof[i], jsObj.FieldByIndex[i].Value]);
NewTreeNode := JsonTree.Items.AddChild(TreeNode,NodeText);
DomToTree(jsObj.FieldByIndex[i], NewTreeNode);
end;
end;
jsList : begin
jsLst := (JsonNode AS TlkJSONlist);
for I := 0 to jsLst.Count - 1 do
begin
NodeText := jsLst.getString(i);
NewTreeNode := JsonTree.Items.AddChild(TreeNode, NodeText);
end;
end;
jsBase :;
jsNumber :;
jsString :;
jsBoolean : ;
jsNull : ;
end;
end;
The problem is, it does not display the items of the "hd_profile_pic_versions" node, which is identified as jsList. It just displays its name but not the contents, and gives an Invalid Class Typecast error message.
Edit
A screenshot of the application for the biography anomaly
Related
How do I parse the following Json in Delphi?
This is my first post, and I've taken care to search as much as I can before asking this question,
so please kindly let me know if I posted wrongly in any way.
I would like to get the value of "name_of_centre" in the "records" array
Thank you for any kind assistance.
procedure TForm1.Button1Click(Sender: TObject);
var
i : integer;
jsonRoot: TJSONValue;
jsonObj: TJSONObject;
jsonArr: TJSONArray;
begin
jsonRoot := TJSONObject.ParseJSONValue(memo2.Lines.text);
try
jsonObj := jsonRoot as TJSONObject;
jsonObj := jsonObj.GetValue('result') as TJSONObject;
jsonArr := jsonObj.GetValue('records') as TJSONArray;
showmessage( jsonArr.Count.ToString ); // works ok
for i := 0 to jsonArr.Count - 1 do
begin
jsonObj := jsonArr.Items[i] as TJSONObject;
showmessage( jsonObj.GetValue('name_of_centre').Value ); // error here
end;
finally
jsonRoot.Free;
end;
end;
I've checked out
Delphi parsing a Json with multiple array types?
How to parse this json data in Delphi 10 Seattle?
(especially this one)
and a few other links... but the JSON format seems different.
Any advice?
{
"help": "testing",
"success": true,
"result": {
"resource_id": "data_resource",
"fields": [
{
"type": "int4",
"id": "_id"
},
{
"type": "text",
"id": "name_of_centre"
},
{
"type": "text",
"id": "location_of_centre"
},
{
"type": "text",
"id": "type_of_centre"
},
{
"type": "text",
"id": "owner"
},
{
"type": "numeric",
"id": "no_of_outlets"
},
{
"type": "numeric",
"id": "no_of_branches"
}
],
"records": [
{
"location_of_centre": "Kings Road",
"no_of_outlets": "12",
"no_of_branches": "0",
"name_of_centre": "Kings Road Centre",
"type_of_centre": "HC",
"owner": "Private",
"_id": 1
},
{
"location_of_centre": "Queens",
"no_of_outlets": "14",
"no_of_branches": "1",
"name_of_centre": "Queens Centre",
"type_of_centre": "HC",
"owner": "Public",
"_id": 2
}
],
"_links": {
"start": "ignore",
"next": "ignore2"
},
"limit": 2,
"total": 10
}
}
Thanks for the many replies.
Olivier : I've included the error in this amended code.
Peter : I tried using
jsonRoot.GetValue('result.records[0].name_of_centre')
and it does give me the value of name_of_centre. Good start.
But I'm hoping to get this code to give me the number of items in Array and I iterate the array, instead of hard-code. Thanks.
Remy : strangely though, it works today. It doesn't get Invalid Typecast
at showmessage( jsonObj.GetValue('name_of_centre').Value );
fpiette : I use Delphi 10.3 RIO.
Thanks to everyone for replying.
Is there a need to use jsonRoot.Free; -- i saw this in a posting on stackoverflow.com... How about jsonObj.Free?
jsonRoot := TJSONObject.ParseJSONValue(memo2.Lines.text);
try
jsonObj := jsonRoot as TJSONObject;
jsonObj := jsonObj.GetValue('result') as TJSONObject;
showmessage( jsonObj.ToString );
jsonArr := jsonObj.GetValue('records') as TJSONArray;
showmessage( jsonArr.Count.ToString );
for i := 0 to jsonArr.Count - 1 do
begin
jsonObj := jsonArr.Items[i] as TJSONObject;
if jsonObj .GetValue('name_of_centre').Value = null then
showmessage('null');
// previously had an Invalid Typecast
showmessage( jsonObj.GetValue('name_of_centre').Value );
end;
finally
jsonRoot.Free;
end;
I want to parse a small JSON file with a nested structure. I am mainly interested in the "name" value, but as there are several name values throughout the document, it would be convenient to have a level depth identifier of some kind.
{
"status": "Tomato",
"name": "ThisIsWhatIwant",
"params": [
{
"name": "THatsNoGood",
"values": [
{
"value": ""
}
]
},
{
"name": "dontlikeiteither",
"values": [
{
"value": ""
}
]
},
{
"name": "Pffff",
"values": [
{
"value": ""
}
]
},
{
"name": "Trump",
"values": [
{
"value": ""
}
]
},
{
"name": "Obama",
"values": [
{
"value": ""
}
]
},
{
"name": "Jackson5",
"values": [
{
"value": ""
}
]
}
],
"NewEden": false,
"Potatoes": []
}
]
Delphi code:
procedure TFmain.json_extract_names(filename: string);
var jsonStr: string;
sr: TStringReader; jtr: TJsonTextReader; s: string;
sl: TSTringList;
I: Integer;
begin
jsonStr := TFile.ReadAllText(FileOpenDialog1.FileName);
sl := Tstringlist.Create;
sr := TStringReader.Create(jsonStr);
try
jtr := TJsonTextReader.Create(sr);
try
while jtr.Read do
begin
s := JsonTokenToString(jtr.TokenType);
if jtr.TokenType = TJsonToken.PropertyName then
begin
if jtr.Value.ToString = 'name' then
begin
jtr.Read;
sl.Add(jtr.Value.AsString);
end
else if jtr.TokenType = TJsonToken.EndObject then
begin
exit;
end;
end;
end;
finally
jtr.Free;
end;
for I := 0 to sl.Count-1 do
begin
ComboBoxsearch.Items.Add(sl[i]);
end;
finally
sr.Free;
sl.Free;
end;
End;
The above code works and my stringlist contains all name values - but I only need the name of the first level! Is there any way to only get the first level name? (in my example JSON the desired result would be: ThisIsWhatIwant)
TJsonTextReader has a Depth property:
Gets the depth of the current token in the JSON document.
Depth returns an integer that represents the nested level of the current token.
For example:
procedure TFmain.json_extract_names(filename: string);
var
jsonStr: string;
sr: TStringReader;
jtr: TJsonTextReader;
sl: TStringList;
begin
jsonStr := TFile.ReadAllText(filename);
sl := TStringList.Create;
try
sr := TStringReader.Create(jsonStr);
try
jtr := TJsonTextReader.Create(sr);
try
while jtr.Read do
begin
if (jtr.Depth = 1) and
(jtr.TokenType = TJsonToken.PropertyName) and
(jtr.Value.ToString = 'name') then
begin
jtr.Read;
sl.Add(jtr.Value.AsString);
end;
end;
finally
jtr.Free;
end;
finally
sr.Free;
end;
ComboBoxsearch.Items.AddStrings(sl);
finally
sl.Free;
end;
End;
I have this Json
{
"Sucess": true,
"Msg": "OK",
"Ret": {
"First": 0,
"Next": true,
"Total": 60,
"Itens": [
{
"ID": 212121,
"Name": "uuuuuuuuuuuuuuuuuuuuuuuu",
"LcID": 9898,
"Oclao": false,
"Lal": {
"ID": 12202,
"Name": "pppppppppppppppppp",
"Pais": "Brasil",
"Dtc": 0.0
},
"Subtipo": {
"ID": 7458,
"Desc": "mnmnmnmnn"
},
"Tipo": {
"Sit": "cor1",
"Sitrm": 0,
"Name": "Shsdfow"
},
"Qtde": 0,
"Qntcoes": 0,
"Pubum": "adfsdfsdfs",
"Evias": {
"arq": {
"Mo": [
"site.com"
],
"Moir": [
"site.com"
]
}
}
},
{
"ID": 9797878,
"Name": "uuuuuuuuuuuuuuuuuuuuuuuu",
"LcID": 9898,
"Oclao": false,
"Lal": {
"ID": 12332,
"Name": "pppppppppppppppppp",
"Pais": "Brasil",
"Dtc": 0.0
},
"Subtipo": {
"ID": 7458,
"Desc": "mnmnmnmnn"
},
"Tipo": {
"Sit": "cor1",
"Sitrm": 0,
"Name": "Shsdfow"
},
"Qtde": 0,
"Qntcoes": 0,
"Pubum": "adfsdfsdfs",
"Evias": {
"arq": {
"Mo": [
"site.com"
],
"Moir": [
"site.com"
]
}
}
}
]
}
}
however, I can only take the values of the first items "Sucess, Msg and Ret"
I'm doing as follows
var
JSONValue, jv: TJSONValue;
joName: TJSONObject;
data: TBytes;
sHtmlResp, sTemp : String;
begin
sHtmlResp := '[' + sHtmlResp + ']';
data := TEncoding.ASCII.GetBytes(sHtmlResp);
JSONValue := TJSONObject.ParseJSONValue(data, 0);
for jv in JSONValue as TJSONArray do
begin
joName := jv as TJSONObject;
sTemp:= joName.Get('Msg').JSONValue.Value;
end;
end;
sHtmlResp contains the string json.
I've tried some other ways to get the most unsuccessful field, how do I get the sub-items as "Ret", "Items" and so on.
A detail, had to add '[' ']' in the JSON string I get, otherwise I can not get even the first fields.
Thank!
Ret is a subobject so you need to access it as such. It has its own values and an array of subobjects, and so on.
Try this:
var
joName, joRet, joItem: TJSONObject;
joItems: TJSONArray;
sHtmlResp, sMsg: String;
bSuccess: Boolean;
begin
sHtmlResp := ...; // original JSON without added braces around it
joName := TJSONObject.ParseJSONValue(sHtmlResp) as TJSONObject;
bSuccess := joName.GetValue('Success') is TJSONTrue;
// if you are using Delphi 10 Seattle or later, you can use this instead:
// bSuccess := (joName.GetValue('Success') as TJSONBool).AsBoolean;
sMsg := joName.GetValue('Msg').Value;
joRet := joName.GetValue('Ret') as TJSONObject;
// use joRet.GetValue() as needed ...
joItems := joRet.GetValue('Itens') as TJSONArray;
for i := 0 to joItems.Count-1 do
begin
joItem := joItems.Items[i] as TJSONObject;
// use joItem.GetValue() as needed ...
end;
end;
If each object/sub-object have unique ObjectID (name).
The simple way is using this function
function ExtractJsonParm(parm:string;h:string):string;
var r:integer;
begin
r:=pos('"'+Parm+'":',h);
if r<>0 then
result := copy(h,r+length(Parm)+4,pos(',',copy(h,r+length(Parm)+4,length(h)))-1)
else
result:='';
end;
Usage:
begin
Writeln(ExtractJsonParm('First',Value));
Writeln(ExtractJsonParm('Next',Value));
Writeln(ExtractJsonParm('Total',Value));
end;
Outputs:
0
true
60
Using jsonDoc it would be
JSON(sHtmlResp)['Msg']
optionally enclosed in VarToStr to enforce type (and also silently convert Null to empty string value).
As noted in the comments by Remy: no need to enclose in [] as this enforces the outer array.
I downloaded the XSuperObject for reading a Json from a web server, but I get a Segmentation fault at moment where I add the Json string to the ISuperArray.
JsonResult : string;
JsonResult := IdHTTP1.Get('http://.................');
LoadJSONXSuperObject(JsonResult);
procedure TDataForm.LoadJSONXSuperObject(S: String);
var
aobj: ISuperArray;
obj2: ISuperObject;
I: Integer;
MyString: String;
begin
aobj := SA(S); // RIGHT HERE I GET THE fault (11) or bus (10)
for I := 0 to aobj.Length-1 do
begin
end;
The following code works, but it takes 2 seconds to read each record which have 17 fields and there is 800 I make the same application in Eclipse it takes 10 seconds for all 800.
try
LResult := LJsonObj.Get('d').JsonValue as TJsonObject;
LElements := LResult.Get('results').JsonValue as TJsonArray;
for i := 0 to LElements.count -1 do
begin
Try
LItem := (LElements.Get(i) as TJsonObject).Get('pbutton').JsonValue as TJsonString;
if LItem <> nil then
PButton := RemoveQuotes(LItem.ToString)
else PButton := '';
except
PButton := '';
End;
Try
LItem := (LElements.Get(i) as TJsonObject).Get('text').JsonValue as TJsonString;
if LItem <> nil then
InvText := RemoveQuotes(LItem.ToString)
else InvText := '';
except
InvText := '';
End;
Try
LItem := (LElements.Get(i) as TJsonObject).Get('buttontext').JsonValue as TJsonString;
if LItem <> nil then
ButtonText := RemoveQuotes(LItem.ToString)
else ButtonText := '';
except
ButtonText := '';
End;
end;
finally
end;
Here is a sample of the Json file.
{
"d": {
"results": [
{
"__metadata": {
"uri": "http://myserver",
"key_fields": "",
"rows_affected": -1,
"last_autoinc": 0
},
"pbutton": 1,
"text": "Pizza",
"buttontext": "Pizza",
"price1": 10.99
},
{
"__metadata": {
"uri": "http://myserver",
"key_fields": "",
"rows_affected": -1,
"last_autoinc": 0
},
"pbutton": 2,
"text": "Pizza 2",
"buttontext": "Pizza 2",
"price1": 10.99
},
{
"__metadata": {
"uri": "http://myserver",
"key_fields": "",
"rows_affected": -1,
"last_autoinc": 0
},
"pbutton": 98,
"text": null,
"buttontext": null,
"price1": 0
}
]
}
}
The sample json you provided shows that you don't get a json array ([data, data, data]), but a json object ({data}). You have to use SO(S) instead of SA(S).
uses
XSuperObject,
XSuperJSON;
procedure TDataForm.LoadJSONXSuperObject(const S: string);
var
jsonObj, currentObj: ISuperObject;
enum: TSuperEnumerator<IJSONAncestor>;
begin
jsonObj:= SO(S);
enum := jsonObj['d.results'].AsArray.GetEnumerator;
while enum.MoveNext do
begin
currentObj := enum.Current.AsObject;
PButton := currentObj.I['pbutton'];
InvText := currentObj.S['text'];
ButtonText := currentObj.S['buttontext'];
// Price := currentObj.F['price1'];
end;
end;
See also the X-SuperObject samples here: https://code.google.com/p/x-superobject/wiki/Sample
coming from .NET, I've been unable to do what I consider a simple task.
I want to use TJSONObject, TJSONArray, TJSONPair etc to construct a simple JSON like the following:
{
"APIKEY": "sadfsafsafdsa",
"UserID": "123123123",
"Transactions:"
[{
"TransactionID": 1,
"Amount": 23
},
{
"TransactionID": 2,
"Amount": 53
}]
}
Logically what I would do is create a TJSONObject and then add 3 TJSONPair, the third pair being TJSONPair of Transactions and a TJSONArrary
However, I am not getting what I wanted. For the Transactions pair, if I convert my transactions TJSONArrary to string, then it comes out as a long string which is invalid.
Any help would be appreciated.
Try this
{$APPTYPE CONSOLE}
{$R *.res}
uses
Data.DBXJSON,
System.SysUtils;
var
LJson, LJsonObject: TJSONObject;
LArr: TJSONArray;
begin
try
ReportMemoryLeaksOnShutdown:=True;
LJsonObject := TJSONObject.Create;
try
LJsonObject.AddPair(TJSONPair.Create('APIKEY', 'sadfsafsafdsa'));
LJsonObject.AddPair(TJSONPair.Create('UserID', '123123123'));
LArr := TJSONArray.Create;
LJson := TJSONObject.Create;
LJson.AddPair(TJSONPair.Create('TransactionID', '1'));
LJson.AddPair(TJSONPair.Create('Amount', '23'));
LArr.Add(LJson);
LJson := TJSONObject.Create;
LJson.AddPair(TJSONPair.Create('TransactionID', '2'));
LJson.AddPair(TJSONPair.Create('Amount', '53'));
LArr.Add(LJson);
LJsonObject.AddPair(TJSONPair.Create('Transactions', LArr));
Write(LJsonObject.ToString);
finally
LJsonObject.Free; //free all the child objects.
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
This will create a JSON like so
{ "APIKEY": "sadfsafsafdsa",
"UserID": "123123123",
"Transactions":
[{
"TransactionID": "1",
"Amount": "23"
},
{
"TransactionID": "2",
"Amount": "53"
}]
}