How to parse a json string response using Delphi - json

I have a rest server returning the next json string:
response:='{"result":["[{\"email\":\"XXX#gmail.com\",\"regid\":\"12312312312312312313213w\"},{\"email\":\"YYYY#gmail.com\",\"regid\":\"AAAAAAA\"}]"]}';
I´d like to parse the response to get a list of all email and regid items.
I have tried the next code but I am getting an AV at (TJSONPair(LItem).JsonString.Value='email')
Any help will be appreciated.
Thanks in advance, Luiz
var
LResult:TJSONArray;
LJsonresponse:TJSONObject;
i:integer;
LItem,jv:TJsonValue;
email,regid:string;
LJsonresponse:=TJSONObject.ParseJSONValue(TEncoding.ASCII.GetBytes(response),0) as TJSONObject;
LResult:=(LJsonresponse.GetValue('result') as TJSONArray);
jv:=TJSONArray(LResult.Get(0));
for LItem in TJSONArray(jv) do begin
if (TJSONPair(LItem).JsonString.Value='email') then begin
email:=TJSONPair(LItem).JsonValue.Value;
end;
if (TJSONPair(LItem).JsonString.Value='regid') then begin
regid:=TJSONPair(LItem).JsonValue.Value;
end;
end;

Your problems start here:
jv := TJSONArray(LResult.Get(0));
The problem is that LResult.Get(0) does not return an instance of TJSONArray. In fact it returns an instance of TJSONString. That string has value:
'[{"email":"XXX#gmail.com","regid":"12312312312312312313213w"},{"email":"YYYY#gmail.com","regid":"AAAAAAA"}]'
It looks like you are going to need to parse this string as JSON to extract what you need. Here is some gnarly code that does that. Please excuse its quality because I have no experience at all with the Delphi JSON parser.
{$APPTYPE CONSOLE}
uses
SysUtils, JSON;
const
response =
'{"result":["[{\"email\":\"XXX#gmail.com\",\"regid\":\"12312312312312312313213w\"},'+
'{\"email\":\"YYYY#gmail.com\",\"regid\":\"AAAAAAA\"}]"]}';
procedure Main;
var
LResult: TJSONArray;
LJsonResponse: TJSONObject;
ja: TJSONArray;
jv: TJSONValue;
begin
LJsonResponse := TJSONObject.ParseJSONValue(response) as TJSONObject;
LResult := LJsonResponse.GetValue('result') as TJSONArray;
ja := TJSONObject.ParseJSONValue(LResult.Items[0].Value) as TJSONArray;
for jv in ja do begin
Writeln(jv.GetValue<string>('email'));
Writeln(jv.GetValue<string>('regid'));
end;
end;
begin
try
Main;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
The big lesson here is to stop using unchecked type casts. Using such casts is asking for trouble. When your data does not match your code, you get unhelpful error messages.

Related

How to create a function to convert a generic "TFPGObjectList" into a "TJsonArray"?

I'm new to fpc Lazarus and came from Delphi BackGround.
I need to create a method in order to convert Generic Object Lists into TJsonArray.
Delphi has it natively, but aparently FPC Lazarus doesn't.
Here is what I have:
generic class function TDAOJsonUtils.ObjectListToJsonArray<T>(aObjectList: T): TJsonArray;
var
I: Integer;
_JsonStr: TJSONStreamer;
_JsonObj: TJsonObject;
begin
result := TJsonArray.Create;
_JsonStr := TJSONStreamer.Create(nil);
try
for i := 0 to Pred(TFPGObjectList(aObjectList).Count) do
begin
_JsonObj := TJsonObject.Create;
end;
finally
FreeAndNil(_JsonStr);
end;
end;
It doesn't compile with and throw the following message at the "for loop" statement:
"generics without specialization cannot be used as a type for a variable"
Any tips?
Thank's in advance.

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

So I'm talking to this webserver, and it's returning me a json entry like this:
{
"result": [
[],
["{\"success\": \"true\", \"Message\":\"User 1 has been deleted.\"}"]
]
}
{"result":[[],["{\"success\": \"true\", \"Message\":\"User 1 has been deleted.\"}"]]}
And I'm having trouble getting things out of it.
Looking at it, I made a jsonobject, pulled the value of result and made it an array, then pulled the second entry of the first array as another array, then took that jsonstring and turned it into another jsonarray, then took the values out.
but for some reason the first jsonarray claims to have two values, both of which are empty. I'm sure there are other errors in my approach past that point as well.
Can I get a hand ironing this thing out?
procedure DeleteUser;
var
aJSON, aResult : String;
aJsonResponse : TJsonObject;
aResultArrayA : TJsonArray;
aResultArrayB : TJsonArray;
aResultArrayC : TJsonArray;
aParsed : TJsonValue;
i : Integer;
Begin
aresult := '{"result":[[],["{\"success\": \"true\", \"Message\":\"User 1 has been deleted.\"}"]]}';
aJsonResponse := TJsonObject.ParseJSONValue(aResult) as TJsonObject;
if not (aJsonResponse is TJsonObject) then
raise Exception.Create('InvalidResponse');
aResultArrayA := aJsonResponse.getValue('result') as TJsonArray;
if aResultArrayA.Count <= 0 then //is 2
raise Exception.Create('InvalidResponse');
aJSON := aResultArrayA.Items[0].Value; // is ''
aJSON := aResultArrayA.Items[1].Value; // is ''
aResultArrayB := aResultArrayA.Items[0] as TJSONArray;
if aResultArrayB.Count <= 0 then // is 0
raise Exception.Create('InvalidResponse'); //raises here
aJSON := aResultArrayB.Items[0].Value;
aJSON := aResultArrayB.Items[1].Value;
aResultArrayC := TJSONObject.ParseJSONValue(aResultArrayB.Items[1].Value) as TJSONArray;
for aParsed in aResultArrayC do begin
aJson := aJson + aParsed.GetValue<string>('success') + ' ';
aJson := aJson + aParsed.GetValue<string>('message') + ' ';
end;
end;
Thanks everyone.
I really think that the best way to work with JSON is serialization and deserialization. Yes, there is some situations when it's better to use parsing, but look at this:
uses ...,Rest.Json;
TMyArray = ARRAY of ARRAY of string;
//class for deserialization outer JSON object
TMyParse = CLASS
private
FResult:TMyArray;
procedure SetResult(const Value: TMyArray);
public
property result:TMyArray read FResult write SetResult;
END;
//class for deserialization inner JSON object
TMyInnerParse = class
private
FSuccess:Boolean;
FMessage:string;
procedure SetMessage(const Value: String);
procedure SetSuccess(const Value: Boolean);
public
property success:Boolean read FSuccess write SetSuccess;
property message:String read FMessage write SetMessage;
end;
procedure DeleteUser;
var
OuterObj: TMyParse;
InnerObj: TMyInnerParse;
aResult: String;
i,j: Integer;
Begin
aResult := '{"result":[[],["{\"success\": \"true\", \"Message\":\"User 1 has been deleted.\"}"]]}';
try
OuterObj := TJson.JsonToObject<TMyParse>(aResult);
if Length(OuterObj.result) > 0 then
for i := 0 to Length(OuterObj.result) - 1 do
if length(OuterObj.result[i]) > 0 then
for j := 0 to Length(OuterObj.result[i]) - 1 do
begin
try
InnerObj := TJson.JsonToObject<TMyInnerParse>(OuterObj.result[i][j]);
//Do your work with result, that in InnerObj now
finally
if assigned(InnerObj) then
FreeAndNil(InnerObj);
end;
end;
finally
if assigned(OuterObj) then
FreeAndNil(OuterObj);
end;
end;
procedure TMyParse.SetResult(const Value: TMyArray);
begin
FResult := value;
end;
procedure TMyInnerParse.SetMessage(const Value: String);
begin
FMessage := value;
end;
procedure TMyInnerParse.SetSuccess(const Value: Boolean);
begin
FSuccess := value;
end;
For cycles in this code are awful, but it's the fastest way to show how you can solve your problem. And it's working.
I don't know what information can be in first empty array and this can be the reason for exceptions. Look at this code as working example, not full solution because lack of information.
It was tested on Delphi 10.1:
P.S. Using arrays are very old way of coding in this situation. But some time ago I met problem with serializing/deserializing TList and TObjectList. I'll try to use them and will return with result.
P.P.S. It tried to use TList, but my attempt fails. Maybe someone can describe how to implement it in a code above.
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.

Delphi HMRC test-api.service.hmrc.gov.uk/hello/world

I'm trying the HMRC Developers HUB tutorials from:
https://developer.service.hmrc.gov.uk/api-documentation/docs/tutorials
I have tried two ways of the "Hello World", but keep getting:
{"code":"ACCEPT_HEADER_INVALID","message":"The accept header is missing or invalid"}
Example 1 REST Client:
procedure TForm1.btnTest_REST_ClientClick(Sender: TObject);
var
jValue: TJSONValue;
begin
RESTClient1.BaseURL := cbHMRC_Test_URLs.Text;
RESTRequest1.Execute;
jValue := RESTResponse1.JSONValue;
MemoContent.Text:= jValue.ToString;
end;
Example 2 TdHTTP:
procedure TForm1.btnTest_HTTPClick(Sender: TObject);
var
get_url: string;
resp: TMemoryStream;
begin
get_url := 'https://test-api.service.hmrc.gov.uk/hello/world';
resp := TMemoryStream.Create;
try
IdHttp1.Request.CustomHeaders.AddValue('Accept', 'application/vnd.hmrc.1.0+json');
IdHTTP1.Get(get_url, resp);
resp.Position := 0; // <-- add this!!
MemoContent.Lines.LoadFromStream(resp);
finally
resp.Free;
end;
end;
Both make the connection, but fail on the Header.
Any ideas about what I'm doing wrong?
I would suggest going with the REST components. I've used them significantly and they work quite well.
On the REST component side, your just missing the Request Accept value:
RESTRequest1.Accept := 'application/vnd.hmrc.1.0+json';
I tested your example to their hello world resource and received:
{"message":"Hello World"}
Looks like it's working.
For those of you struggling like me with how to implement the initial HMRC tutorials in Delphi, try the following.
Create a new application. I chose a Multi-Device / Blank Application option.
On the main form, add the following components:-
TRESTClient
TRESTRequest
TRESTResponse
TMemo
TButton
Add the System.JSON unit to the uses clause.
Set the Button1Click procedure as follows:-
procedure TForm1.Button1Click(Sender: TObject);
var
jValue: TJSONValue;
begin
RESTClient1.BaseURL := 'https://test-api.service.hmrc.gov.uk/hello/world';
RESTRequest1.Accept := 'application/vnd.hmrc.1.0+json';
RESTRequest1.Execute;
jValue := RESTResponse1.JSONValue;
Memo1.Text:= jValue.ToString;
end;
Run the program, click the button and voila!
I hope this helps someone

Get specific Value from Json String Delphi XE8

I have this Json String in Delphi,
{
"bpd": {
"euro": {
"buying_rate": "48.50",
"selling_rate": "52.70"
},
"dollar": {
"buying_rate": "45.30",
"selling_rate": "45.80"
},
"source": "https://www.popularenlinea.com/_api/web/lists/getbytitle('Rates')/items"
},
"blh": {
"euro": {
"buying_rate": "48.50",
"selling_rate": "52.00"
},
"dollar": {
"buying_rate": "45.35",
"selling_rate": "45.80"
},
"source": "http://www.blh.com.do/Inicio.aspx"
}
}
I want extract the buying_rate and selling_rate for the dollar for the bank blh
i try this but i get AV
var
LJsonObj : TJSONObject;
LRows, LElements, LItem : TJSONValue;
begin
LJsonObj := TJSONObject.ParseJSONValue(TEncoding.ASCII.GetBytes(s),0) as TJSONObject;
try
LRows:=LJsonObj.Get(0).JsonValue;
LElements:=TJSONObject(TJSONArray(LRows).Get(0)).Get(0).JsonValue;
LItem :=TJSONObject(TJSONArray(LElements).Get(0)).Get(0).JsonValue;
ShowMessage(TJSONObject(LItem).Get('buying_rate').JsonValue.Value);
finally
LJsonObj.Free;
end;
There are many errors in your code. The most significant is your repeated use of unchecked casts. When you write
TJSONArray(LRows)
you are telling the compiler that you know 100% for sure that LRows is descended from TJSONArray. Well, it isn't. Especially when you are dealing with external data you must not make such assumptions. You are then subject to the whims of the data that you receive. Use a checked cast instead
LRows as TJSONArray
Now, that's still wrong because LRows is not an array. In fact your JSON doesn't have any arrays at all. It just has objects. But when you use the checked cast the failure will be a meaningful error rather than an access violation.
This program reads the values you are looking for:
{$APPTYPE CONSOLE}
uses
System.SysUtils, System.JSON, System.IOUtils;
procedure Main;
var
s: string;
LJsonObj: TJSONObject;
blh: TJSONObject;
dollar: TJSONObject;
rate: TJSONString;
begin
s := TFile.ReadAllText('C:\desktop\json.txt');
LJsonObj := TJSONObject.ParseJSONValue(TEncoding.UTF8.GetBytes(s), 0) as TJSONObject;
try
blh := LJsonObj.GetValue('blh') as TJSONObject;
dollar := blh.GetValue('dollar') as TJSONObject;
rate := dollar.GetValue('buying_rate') as TJSONString;
Writeln(rate.Value);
rate := dollar.GetValue('selling_rate') as TJSONString;
Writeln(rate.Value);
finally
LJsonObj.Free;
end;
end;
begin
try
Main;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Output
45.35
45.80
I advise you to spend some time at the JSON site to make sure that you have a very clear understanding of the terminology. You should have a clear understanding of what is meant by the terms object, array and value. At the moment I think that is lacking.
If you use jsonDoc it would look something like this:
StrToFloat(JSON(JSON(JSON(TFile.ReadAllText('C:\desktop\json.txt'))['blh'])['dollar'])['buying_rate'])

JSON parsing "NULL"

I have this code working until today. If there is for egxample this:
"status":null
there will be exception "Invalid class type". How to fix it? Thanks for help.
procedure TForm1.Button10Click(Sender: TObject);
var
IdHTTP: TIdHTTP;
IdSSL: TIdSSLIOHandlerSocketOpenSSL;
JSON: string;
jsonObiekt: TJSONObject;
streams: TJSONArray;
stream: TJSONObject;
channel: TJSONObject;
status: TJSONString;
liczbaStrumieni: integer;
i: integer;
begin
IdHTTP := TIdHTTP.Create;
try
IdSSL := TIdSSLIOHandlerSocketOpenSSL.Create(IdHTTP);
IdHTTP.IOHandler := IdSSL;
IdHTTP.Request.Accept := 'application/vnd.twitchtv.v3+json';
IdHTTP.Request.CustomHeaders.AddValue('Client-ID', 'smb61nyd0vxmqdn9d3k735qbx41cdyg');
JSON := IdHTTP.Get('https://api.twitch.tv/kraken/streams?game=StarCraft:%20Brood%20War');
finally
IdHTTP.Free;
end;
jsonObiekt := TJSONObject.ParseJSONValue(JSON) as TJSONObject;
try
streams := jsonObiekt.Get('streams').JsonValue as TJSONArray;
liczbaStrumieni := streams.Size;
for i := 0 to liczbaStrumieni - 1 do
begin
stream := streams.Get(i) as TJSONObject;
channel := stream.Get('channel').JsonValue as TJSONObject;
status := channel.Get('status').JsonValue as TJSONString;
Memo6.Lines.Add(status.Value);
end;
finally
jsonObiekt.Free;
end;
end;
I see from the comments that you're familiar with the JSON specification on JSON.org. Your problem appears to be with understanding how it maps to the DBXJSON model.
Specifically, TJSONObject represents an Object as defined by the JSON standard. It does not mean "a (Delphi) object that holds JSON data." That's what TJSONValue is there for. Try using that instead.