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

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

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.

Memory leak When Returning TJSONVALUE from Function

I have a JSON array of objects that I iterate. From each element of the array I retrieve further json data via a function that returns a JSONValue. I then must add that returned jsonvalue to the specific array element.
It works perfectly except that Delphi is reporting a memory leak on shutdown and I just can't find it. I'm pretty new to JSON and I've spent all day on this. Can someone help by suggesting the right way to go about this. Simplified code below. Many Thanks.
function TForm1.Function1(ThisProductJSON: String): Integer;
Var
MultiObj : TJSONObject;
OtherValues : TJSONVALUE;
ThisObject : TJSONObject;
I : Integer;
jsArr : TJSONARRAY;
S:String;
Stockcode : String;
begin
MultiObj:=TJSONObject.Create(nil);
s:='[{"ThisElement":1,"Thiscode":12345,"ThisBarcode":"2345678901231","Price":1.00,"Measure":"EA","Description":"Some Description"},'+
'{"ThisElement":2,"Thiscode":21345,"ThisBarcode":"3124567890123","Price":2.00,"Measure":"EA","Description":"Some Description"},'+
'{"ThisElement":3,"Thiscode":31345,"ThisBarcode":"6123457890123","Price":3.00,"Measure":"EA","Description":"Some Description"},'+
'{"ThisElement":4,"Thiscode":41345,"ThisBarcode":"9123456780123","Price":4.00,"Measure":"EA","Description":"Some Description"},'+
'{"ThisElement":5,"Thiscode":51345,"ThisBarcode":"8234567901235","Price":5.00,"Measure":"EA","Description":"Some Description"}]';
ThisProductJSON:=S;
try
try
MultiObj.AddPair('ThisName','ThisValue');
MultiObj.AddPair('AnotherName','AnotherValue');
I:=0;
JSArr:=TJSONObject.ParseJSONValue(ThisProductJSON).AsType<TJSONARRAY>;
//Process all products in the array with a call to detailed Rest for each product
begin
for I := 0 to jsArr.count-1 do //iterate through the array
begin
Stockcode:= jsArr.Items[I].GetValue<string>('Thiscode');
ThisObject:=TJSONObject(jsArr[i]); // Make ThisObject Point to the jsArr[i] as an object so I can add the Other Values to this array element
OtherValues :=(GetOtherDetails(Stockcode)); // Call the below function to return the JSONVALUE containing addtional data
ThisObject.AddPair('OtherDetails',OtherValues); // Add all of the additional data in OtherValues JSONVALUE to this element of array via the object
end;
MultiObj.AddPair('WoWMultiProduct',JSARR); // This MultiObj hokds the consolidated data
end;
except
On E: Exception do
begin
//Errror handling here
end;
end;
finally
MultiObj.Free;
OtherValues.Free; // <<- I thought this would free the Result of the function but I think its leaking
JSARR.Free;
if assigned(ThisObject) then ThisObject.Free;
end;
end;
function TForm1.GetOtherDetails(Stockcode: String): TJSONVALUE;
Var
ThisResponseObject, MYRESULT : TJSONOBJECT;
vNIP, vCOO : TJSONVALUE;
begin
DetailedRESTRequest.execute; //<-- This REST Service Returns aditional JSON data
MyResult:=TJSONObject.Create;
if DetailedRESTResponse.StatusCode=200 then
begin
Try
Try
ThisResponseObject := TJSONObject.ParseJSONValue(DetailedRESTResponse.content)as TJSONObject; // Resd the entire JSON Response into TJSONObject
vCOO:=ThisResponseObject.Getvalue('COO'); // Get First JSON Value I need
vNIP:=ThisResponseObject.Getvalue('Nip'); // Get Next JSON Value I Need
MyResult.AddPair('NIPInfo',vNip); // Thiis is the only way I know how to Add the Values
MyResult.AddPair('COO',vCOO); // So that I can get them both together in the Result
Result:= TJSONObject.ParseJSONValue(MyResult.ToJSON)as TJSONValue; // Get back the JSONValue from the function
Except
On E:Exception do
begin
// Some Error Handling Here e.Classname+' ' +E.Message ;
end;
End;
Finally
If Assigned(ThisResponseObject) then ThisResponseObject.Free;
// NOTE IF I FREE MYRESULT OBJECT I GET ACCESS VIOLATION WHEN I TRY TO USE THE RESULT OF THE FUNCTION
End;
end;
end;
JSON objects are a tree-like data structure. When you request a value from a node, with getvalue (for example), it is actually giving you a reference to that node's object.
so, when you do the following in the "GetOtherDetails" function:
vCOO:=ThisResponseObject.Getvalue('COO');
MyResult.AddPair('COO',vCOO);
You make ThisResponseObject and MyResult share nodes (memory locations), so when you free one of them the other will try to free memory locations that no longer exist and generate the access violation
ThisResponseObject.free;
MyResult.Free; //access violation
Similarly, on "Function1" when doing:
ThisObject:=TJSONObject(jsArr[i]);
OtherValues :=(GetOtherDetails(Stockcode));
ThisObject.AddPair('OtherDetails',OtherValues);
You're making ThisObject contain OtherValues object... so when you try to free the two objects you're going to run into memory problems.

Base64 is breaking lines when encoding in Delphi [duplicate]

This question already has answers here:
Convert BitMap to string without line breaks?
(2 answers)
Closed 3 years ago.
I am encoding an image to Base64 using the following code snippet in delphi.
procedure TWM.WMactArquivosAction(Sender: TObject; Request: TWebRequest;
Response: TWebResponse; var Handled: Boolean);
var
ImagePath: string;
JsonObject: TJSONObject;
inStream, outStream: TStream;
StrList: TStringList;
begin
inStream := TFileStream.Create(ImagePath, fmOpenRead);
try
outStream := TFileStream.Create('final_file', fmCreate);
JsonObject := TJSONObject.Create;
try
TNetEncoding.Base64.Encode(inStream, outStream);
outStream.Position := 0;
StrList := TStringList.Create;
StrList.LoadFromStream(outStream);
JsonObject.AddPair('file', StrList.Text);
finally
Response.Content := JsonObject.ToString;
outStream.Free;
JsonObject.DisposeOf;
end;
finally
inStream.Free;
end;
end;
It works fine, the file is converted to Base64 and added to the JsonObject.
The problem is that when retrieving this JsonObject from the webserver I get a bad json formatted because there are line breaks in the base64 string.
You can see that the red one is the string. After the first line break the json is disturbed and it shows in blue, meaning that there is an error in the json response.
The problem
So, the problem is that when encoding to Base64 it is adding line breaks to the string and this is not supported in Json.
My Guess
I have a guess, which indeed worked but I am not sure this is the best solution.
I looped through all the Strings in the TStringList and added the data into a TStringBuilder. After all, I added the TStringBuilder to the Json. Look at my code.
...
var
...
StrBuilder: TStringBuilder;
begin
...
try
...
StrList.LoadFromStream(outStream);
// New
StrBuilder := TStringBuilder.Create;
for I := 0 to StrList.Count - 1 do
StrBuilder.Append(StrList.Strings[I]);
JsonObject.AddPair('file', StrBuilder.ToString);
finally
Response.Content := JsonObject.ToString;
...
end;
...
end;
As you can see the JSON is fine now.
The question
Looping through all the items seems a bad solution for me, will it work fine? (It is getting the response in 344ms on localhost)
Is there a better solution?
Instead of the convenience instance TNetEncoding.Base64 create your own instance and specify the CharsPerLine parameter in Create with 0.
encoding := TBase64Encoding.Create(0);
try
encosing.Encode(inStream, outStream);
finally
encoding.Free;
end;

Passing a JSON object to Datasnap Server application

I have a datasnap application server and a client witten in Delphi Tokyo 10.2. I need to know whether I do the communication between the two correctly. I will write down the client code here :
Client Code:
procedure TRemoteAcessModule.InitialiseRESTComponents(BaseURL: string);
var
ReqParam : TRESTRequestParameter;
begin
//URL to which client has to connect
RESTClient1.BaseURL := BaseURL;//'http://192.168.110.160:8080/datasnap/rest/TserverMethods1';
RESTClient1.Accept:= 'application/json, text/plain; q=0.9, text/html;q=0.8,';
RESTClient1.AcceptCharset := 'UTF-8, *;q=0.8';
RESTRequest1.Client := RESTClient1;
RESTRequest1.Response := RESTResponse1;
RESTResponse1.RootElement := 'Rows';
RESTResponseDataSetAdapter1.Response := RESTResponse1;
RESTResponseDataSetAdapter1.Dataset := FDMemTable1;
RESTResponseDataSetAdapter1.NestedElements := true;
end;
class function TItemsHelper.InsertItem(item: TItem): boolean;
var
ds : TDataset;
begin
ds := RemoteAcessModule.CallResource2('InsertItem', TJson.ObjectToJsonString(item));
if ds.Fields[0].AsInteger > 0 then
result := true
else
result := false
end;
function TRemoteAcessModule.CallResource2(ResourceName: string): TDataset;
begin
CallResourceNoParams(ResourceName);
result := GetQueryResult;
end;
procedure TRemoteAcessModule.CallResource(ResourceName, Params: string);
begin
RESTResponseDataSetAdapter1.Dataset := TFDMemTable.Create(self); //new
RESTRequest1.Resource := ResourceName;
RESTRequest1.ResourceSuffix := '{qry}';
RESTRequest1.AddParameter('qry', TIdURi.ParamsEncode(Params), TRESTRequestParameterKind.pkURLSEGMENT, [poAutoCreated]);
RESTRequest1.Execute;
RESTResponseDataSetAdapter1.Active := true;
RESTRequest1.Params.Delete('qry');
RESTRequest1.ResourceSuffix :='';
end;
At server side, I have written a function which decodes the json and inserts the item data into the database.
So, to insert an item, i have call TItemsHelper.InsertItem by passing the Titem object i need to insert. This works. But the doubts I have are :
RESTRequest1.AddParameter('qry', TIdURi.ParamsEncode(Params), TRESTRequestParameterKind.pkURLSEGMENT, [poAutoCreated]);
is the way mentioned above the correct method to pass the encoded
json object to server (making it a part of the URL ) ? If not how
can i send json data to datasnap server ?
Can I get some advise on things I have done incorrectly here ?
Thanks in advance for your patience to go through this. Looking forward for some guidance.
If you have a DataSnap REST Server, there is a wizard to build DataSnap client application. The REST Client Library you use is for generic REST server. If you have DataSnap Server, and you want Delphi client application, you can take advantage to use wizard DataSnap REST Client Module.
BTW, Embarcadero now recommends RAD Server to develop REST server.
As #erwin Mentioned you should check the DataSnap REST Client Module. At least that's how I have set up my Proof of Concept application I made for a client. For example the DataSnap Server has a few REST Client Module which has a public method called GetListByFetchParams which returns a TFDJSONDataSets. The implementation looks a bit like this :
function TBaseServerMethods.InternalGetListByFetchParams(aFetchParams: TFetchParams): TFDJSONDataSets;
begin
{$IFDEF USE_CODESITE}
CSServerMethods.TraceMethod( Self, 'InternalGetListByFetchParams', tmoTiming );
CSServerMethods.Send( csmFetchParams, 'aFetchParams', aFetchParams );
{$ENDIF}
Result := TFDJSONDataSets.Create;
// Close the ListDataSet
ListDataSet.Close;
// Open the List DataSet
ListDataSet.Open;
// Add the ListDataSet to the result using a predefined name.
TFDJSONDataSetsWriter.ListAdd( Result, ListDataSetName, ListDataSet );
{$IFDEF USE_CODESITE}
CSServerMethods.Send( 'qryList.SQL', ListDataSet.SQL );
CSServerMethods.Send( csmFDDataSet, 'qryList DataSet Contents', ListDataSet );
{$ENDIF}
end;
The most important thing is that I create a TFDJSONDataSets which I set up as th result. I removed some code but in short the aFetchParams is used to alter the SQL Statement of the FireDaC Query component which is referenced by ListDataSet and it then Opens that dataset. Next I use a TFDJSONDataSetsWriter.ListAdd call to add our ListDataSet to the TFDJSONDataSets which is the result of the function.
Client Side I need some code too. This code will call the remote method on the DataSnapServer which will return the TFDJSONDataSets and which I can then deserialise into the TFDMemTables on the client :
procedure TBaseAgent.FetchListWithParams(aFetchParams : TFetchParams);
var
oListData : TFDJSONDataSets;
oKeyValues : TDictionary< String, String >;
begin
{$IFDEF USE_CODESITE}CSAgent.TraceMethod( Self, 'FetchListWithParams', tmoTiming );{$ENDIF}
oListData := FetchListWithParamsFromServer( aFetchParams );
try
tblList.AppendData( TFDJSONDataSetsReader.GetListValue( oListData, 0 ) );
tblList.Open;
finally
FreeAndNil( oListData );
end;
end;

How to parse a json string response using Delphi

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.