I need to get the subitem1 value of a LResponse that contains following JSON:
"Information": {
"subitem1": "2011",
"subitem2": "Test"
}
I am using this code to get the other values, it's working good, but when I try to get the Information, subitem1 or subitem2, it's returning a empty value.
var
LClient: TRESTClient;
LRequest: TRESTRequest;
LResponse: TRESTResponse;
begin
LClient := TRESTClient.Create(URL_API);
try
LRequest := TRESTRequest.Create(LClient);
try
LResponse := TRESTResponse.Create(LClient);
try
LRequest.Client := LClient;
LRequest.Response := LResponse;
LRequest.Method := rmGET;
LRequest.Params.AddHeader('Authorization','Bearer '+FToken);
LRequest.Params.ParameterByName('Authorization').Options := [poDoNotEncode];
LRequest.Params.AddHeader('Accept', 'application/json');
LRequest.Execute;
LResponse.GetSimpleValue('subitem1', FLogradouro);
{ ... }
GetSimpleValue is only able to read top level properties of JSON in response. It is also extremely inefficient, because it parses JSON upon every call to the method.
TRESTResponse already gives you access to parsed JSON via its JSONValue property. It allows you to query values within the whole structure using JSON path. Accessing JSONValue property will parse the response only once, but beware that it can return nil, if the response is empty or not a valid JSON.
Another point is that you don't have to create TRestResponse yourself. It is automatically created in LRequest.Execute. With that said the code to access values in the returned JSON would be:
if not Assigned(LRequest.Response.JSONValue) then
raise Exception.Create('Invalid response.');
ShowMessage(LRequest.Response.JSONValue.GetValue<string>('Information.subitem1'));
You can use square brackets in JSON path as array accessor to access items by index:
LRequest.Response.JSONValue.GetValue<string>('SomeStrings[0]');
You can do:
RESTResponse1.JSONValue.GetValue<string>('Information.subitem1');
or
var
LValue: string;
LJSONObject: TJSONObject;
begin
LJSONObject := RESTResponse1.JSONValue as TJSONObject;
LValue := LJSONObject.GetValue('subitem1').Value;
end;
Related
I'm trying to utilize the OVH API in Delphi using the REST client. For this OVH requires me to generate a signature, but their documentation does not provide much info on this other than:
"$1$" + SHA1_HEX(AS+"+"+CK+"+"+METHOD+"+"+QUERY+"+"+BODY+"+"+TSTAMP)
They do provide thin wrappers for other languages so I thought I could take a look at those and try to replicate it. I found the following for generating the signature in C# and have extracted the function to be used in a test application.
Test app C# code:
textBox1.Text = GenerateSignature("appSecret", "consKey", 123456789, "PUT", "/path/to/api", "TEST DATA");
The C# result is:
$1$8336ecc5d03640b976e0b3ba005234a3046ab695
I attempted the rewrite the function in Delphi and came up with the following function:
function GenerateSignature(const appSecret, consKey: string;
const currentTimeStamp: LongInt; const method, target: string;
const data: string = ''): string;
begin
var
toSign := string.Join('+', [appSecret, consKey, method, target, data,
currentTimeStamp]);
var
binaryHash := THashSHA1.GetHashBytes(toSign);
var
signature := '';
for var byte in binaryHash do
begin
signature := signature + byte.ToHexString.ToLower;
end;
Result := '$1$' + signature;
end;
And to test it:
procedure Main;
const
APP_SECRET = 'appSecret';
CONSUMER_KEY = 'consKey';
method = 'PUT';
target = '/path/to/api';
data = 'TEST DATA';
CURRENT_TIMESTAMP = 123456789;
begin
Writeln(GenerateSignature(APP_SECRET, CONSUMER_KEY, CURRENT_TIMESTAMP,
method, data));
end;
Both test applications in C# and in Delphi use the same data but produce different outputs. My expected output is:
$1$8336ecc5d03640b976e0b3ba005234a3046ab695
But I end up getting the following output from delphi:
$1$d99fd5086853e388056d6fe37a9e2d0723de151b
I do not know C# very well but it seems to get the hashbytes then convert it to hex and stitch it together. How can I modify the Delphi function I wrote so that I can get my expected result?
Thanks to the last parameter being optional you didn't notice (because no compiler error/warning) that you actually missed one parameter when calling/testing your function, resulting in a text of
'appSecret+consKey+PUT+TEST DATA++123456789' to be hashed. Which is indeed
d99fd5086853e388056d6fe37a9e2d0723de151b
Let me reformat your test to make it more obvious:
const
APP_SECRET = 'appSecret';
CONSUMER_KEY = 'consKey';
CURRENT_TIMESTAMP = 123456789;
method = 'PUT';
target = '/path/to/api'; // Where is this used?
data = 'TEST DATA';
begin
GenerateSignature
( APP_SECRET
, CONSUMER_KEY
, CURRENT_TIMESTAMP
, method
// Forgotten parameter
, data // becomes "target"
);
end;
Consider making const data: string = '' mandatory, too, instead of optional.
I need to check if there has been a change in a certain part of the application and therefore I make "copies" of the data after loading them and then compare them. One part of the comparison function involves checking keys in dictionaries like lDict1.Keys.EqualsTo(lDict2.Keys).
Although the dictionaries do not rely on the order of the elements, I didn't realize that even if I fill two dictionaries with the same data, they won't be created the same and the order of elements may change, so the previous function does not work properly because it relies on the elements order that may not match when using any of the following methods. (I'm not sure why)
var
lDict1, lDict2 : IDictionary<Integer, TObject>;
lKey : Integer;
begin
lDict1 := TCollections.CreateDictionary<Integer, TObject>;
lDict1.Add(5, nil); // Keys.First = 5, Keys.Last = 5
lDict1.Add(6, nil); // Keys.First = 5, Keys.Last = 6
lDict2 := TCollections.CreateDictionary<Integer, TObject>;
lDict2.AddRange(lDict1); // Keys.First = 6, Keys.Last = 5
lDict2.Clear;
for lKey in lDict1.Keys do // Keys.First = 6, Keys.Last = 5
lDict2.Add(lKey, nil);
end;
Is there any way to make an exact copy of the dictionary so I can compare them? One way to work around this problem is to create my own comparison function, but I'd like to avoid that.
function ContainsSameValues<T>(AEnumerable1, AEnumerable2: IEnumerable<T>): Boolean;
var
lValue : T;
begin
Result := AEnumerable1.Count = AEnumerable2.Count;
if Result then
begin
for lValue in AEnumerable1 do
begin
Result := AEnumerable2.Contains(lValue);
if not Result then
Exit;
end;
end;
end;
usage
ContainsSameValues<Integer>(lDict1.Keys, lDict2.Keys);
Checking for equality of a unordered dictionaries is a relatively simple algorithm. I will outline it here. Suppose we have two dictionaries, A and B.
Compare the number of elements of A and B. If this differs, the dictionaries are not equal.
Enumerate each key/value pair k,v in A. If k is not in B, or B[k] is not equal to v, then the dictionaries are not equal.
If you reach the end of the enumeration, then you know that the dictionaries are equal.
I have a C DLL with a number of functions I'm calling from Delphi. One of the functions (say Func1) returns a pointer to a struct - this all works fine. The structs created by calling Func1 are stored in a global pool within the DLL. Using a second function (Func2) I get a pointer to a block of memory containing an array of pointers, and I can access the array elements using an offset.
I need to be able copy the address in the returned pointer for a struct (from Func1) to any of the memory locations in the array (from Func2). The idea is that I can build arrays of pointers to pre-defined structs and access the elements directly from Delphi using pointer offsets.
I tried using:
CopyMemory(Pointer(NativeUInt(DataPointer) + offset), PStruct, DataSize);
where DataPointer is the start of my array and PStruct is returned from Func1, but that doesn't copy the address I need.
In .NET it works using Marshal.WriteIntPtr and looking at the underlying code for this using Reflector I think I need something trickier than CopyMemory. Anyone got any ideas for doing this in Delphi?
Edit: This is part of a wrapper around vector structures returned from the R language DLL. I have a base vector class from which I derive specific vector types. I've got the wrapper for the numeric vector working, so my base class looks fine and this is where I get DataPointer:
function TRVector<T>.GetDataPointer: PSEXPREC;
var
offset: integer;
h: PSEXPREC;
begin
// TVECTOR_SEXPREC is the vector header, with the actual data behind it.
offset := SizeOf(TVECTOR_SEXPREC);
h := Handle;
result := PSEXPREC(NativeUInt(h) + offset);
end;
Setting a value in a numeric vector is easy (ignoring error handling):
procedure TNumericVector.SetValue(ix: integer; value: double);
var
PData: PDouble;
offset: integer;
begin
offset := GetOffset(ix); // -- Offset from DataPointer
PData := PDouble(NativeUInt(DataPointer) + offset);
PData^ := value;
end;
For a string vector I need to (i) create a base vector of pointers with a pre-specified length as for the numeric vector (ii) convert each string in my input array to an R internal character string (CHARSXP) using the R mkChar function (iii) assign the address of the character string struct to the appropriate element in the base vector. The string array gets passed into the constructor of my vector class (TCharacterVector) and I then call SetValue (see below) for each string in the array.
I should have thought of PPointer as suggested by Remy but neither that or the array approach seem to work either. Below is the code using the array approach from Remy and with some pointer vars for checking addresses. I'm just using old-fashioned pointer arithmetic and have shown addresses displayed for a run when debugging:
procedure TCharacterVector.SetValue(ix: integer; value: string);
var
PData: PSEXPREC;
offset: integer;
offset2: integer;
PTest: PSEXPREC;
PPtr: Pointer;
PPtr2: Pointer;
begin
offset := GetOffset(ix);
PPtr := PPointer(NativeUInt(DataPointer) + offset); // $89483D8
PData := mkChar(value); // $8850258
// -- Use the following code to check that mkChar is working.
offset2 := SizeOf(TVECTOR_SEXPREC);
PTest := PSEXPREC(NativeUInt(PData) + offset);
FTestString := FTestString + AnsiString(PAnsiChar(PTest));
//PPointerList(DataPointer)^[ix] := PData;
//PPtr2 := PPointer(NativeUInt(DataPointer) + offset); // Wrong!
PPointerArray(DataPointer)^[ix] := PData;
PPtr2 := PPointerArray(DataPointer)^[ix]; // $8850258 - correct
end;
I'd have thought the address in PData ($8850258) would now be in PPtr2 but I've been staring at this so long I'm sure I'm missing something obvious.
Edit2: The code for SetValue used in R.NET is as follows (ignoring test for null string):
private void SetValue(int index, string value)
{
int offset = GetOffset(index);
IntPtr stringPointer = mkChar(value);
Marshal.WriteIntPtr(DataPointer, offset, stringPointer);
}
From reflector, Marshal.WriteIntPtr uses the following C:
public static unsafe void WriteInt32(IntPtr ptr, int ofs, int val)
{
try
{
byte* numPtr = (byte*) (((void*) ptr) + ofs);
if ((((int) numPtr) & 3) == 0)
{
*((int*) numPtr) = val;
}
else
{
byte* numPtr2 = (byte*) &val;
numPtr[0] = numPtr2[0];
numPtr[1] = numPtr2[1];
numPtr[2] = numPtr2[2];
numPtr[3] = numPtr2[3];
}
}
catch (NullReferenceException)
{
throw new AccessViolationException();
}
}
You say you want to copy the struct pointer itself into the array, but the code you have shown is trying to copy the struct data that the pointer is pointing at. If you really want to copy just the pointer itself, don't use CopyMemory() at all. Just assign the pointer as-is:
const
MaxPointerList = 255; // whatever max array count that Func2() allocates
type
TPointerList = array[0..MaxPointerList-1] of Pointer;
PPointerList = ^TPointerList;
PPointerList(DataPointer)^[index] := PStruct;
Your use of NativeUInt reveals that you are using a version of Delphi that likely supports the {$POINTERMATH} directive, so you can take advantage of that instead, eg:
{$POINTERMATH ON}
PPointer(DataPointer)[index] := PStruct;
Or, use the pre-existing PPointerArray type in the System unit:
{$POINTERMATH ON}
PPointerArray(DataPointer)[index] := PStruct;
I have used the following code to create the XML Document :
procedure TForm1.btnCreateXMLClick(Sender: TObject);
var
rootName:string;
childName:string;
attrChild:string;
iXml: IDOMDocument;
iRoot, iNode, iNode2, iChild, iAttribute: IDOMNode;
begin
XMLDoc.Active:=false;
XMLDoc.XML.Text:='';
XMLDoc.Active:=true;
XMLDoc.FileName:='C:\Documents and Settings\a\Desktop\New Text Document.xml';
iXml := XmlDoc.DOMDocument;
//iRoot:=iXml.documentElement(iXml.createElement('xml'));
iRoot := iXml.appendChild(iXml.createElement ('xml'));
// node "test"
iNode := iRoot.appendChild (iXml.createElement ('test'));
iNode.appendChild (iXml.createElement ('test2'));
iChild := iNode.appendChild (iXml.createElement ('test3'));
iChild.appendChild (iXml.createTextNode('simple value'));
iNode.insertBefore (iXml.createElement ('test4'), iChild);
// node replication
iNode2 := iNode.cloneNode (True);
iRoot.appendChild (iNode2);
// add an attribute
iAttribute := iXml.createAttribute ('color');
iAttribute.nodeValue := 'red';
iNode2.attributes.setNamedItem (iAttribute);
// show XML in memo
memXMLOutput.Lines.Text:=FormatXMLData(XMLDoc.XML.Text);
end;
I get the output in memXMLOutput but the XML document does not show the output when seen in Notepad ot IE. where is the problem? Thanks in advance
Remove this:
XMLDoc.FileName:='C:\Documents and Settings\a\Desktop\New Text Document.xml';
and add something like this after the code is done creating the XML document:
XMLDoc.SaveToFile('C:\Documents and Settings\a\Desktop\New Text Document.xml');
I have a class defined as:
TfraFrame = class(TFrame);
then I have several subclasses all inherting from this, such as:
TfraUsers = class(TfraFrame);
TfraGroups = class(TfraFrame);
TfraMenus = class(TfraFrame);
In my main form i have declared variables as:
var
fraUsers: TfraUsers;
fraGroups: TfraGroups;
fraMenus: TfraMenus;
Now my question is, I would like to have one single function to control the generation of the instances of each class. There should only be once instance of each class, but i only want to create the instances if the user requires them.
I would like to pass the variable like this:
procedure ShowFrame(Frame: TfraFrame)
begin
if Frame = nil then
begin
Frame := TfraFrame.Create(self);
Frame.Init(Panel1);
end;
Frame.Show;
end;
and call it like this;
ShowFrame(fraUsers);
I was expecting it to create a instance of TfraUsers (because that is what fraUsers is declared as), however, i suspect it may be creating an instance of fraFrame.
Is there a way to create an instance of the type that the variable was declared as?
Yep, you need to tell showFrame which class to instantiate:
add
type
tfraFrameClass = class of TfraFrame;
just after the declaration of tfraFrame. And change showFrame to:
function ShowFrame(Frame: TfraFrame; FrameClass: TfraFrameClass): TfraFrame;
begin
if Frame = nil then
begin
Result := FrameClass.Create(self);
Result.Init(Panel1);
end
else
Result := Frame;
Result.Show;
end;
Note that you cannot pass Frame as a var parameter as the compiler will insist on declared and actual types of the passed parameter to be exactly the same.
Update
Updated example to show frame when it was already assigned.
Update
I neglected to mention that assigning the result of the Create call in the OP's code for the showFrame procedure wouldn't work. If it wasn't declared as var, the assignment would not go beyond the scope of the showFrame procedure, the value of the variable passed into showFrame would not be changed. Declaring it as a var would not work either as mentioned above. The solution is to do what #Andreas suggests: use an untyped pointer; or make it a function as I did. Of course (grin) I prefer mine as that preserves type safety just a little bit better.
Also, of course in my example the intention was to have the result of the showFrame function assigned to the appropriate frame variable, like so:
fraUsers := showFrame(fraUsers, TfraUsers);
Update
Or, as Sertac pointed out, you can still use procedure with a var parameter when you do a cast on the variable you pass to showForm.
procedure showFrame(var Frame: TfraFrame; FrameClasse: TfraFrameClass);
begin
if Frame = nil then
begin
Frame := FrameClass.Create(self);
Frame.Init(Panel1);
end;
Frame.Show;
end;
and call it as:
showFrame(TfraFrame(fraUsers), TFraUsers);
I suggest that you make your frame variables properties
property fraUsers: TfraUsers read GetfraUsers write ffraUsers;
..
function GetFraUsers: TFraUsers;
begin
if fFraUsers = nil then
fFraUsers := TfraUsers.Create(...);
Result := ffraUsers;
end;
Then
procedure ShowFrame(Frame: TfraFrame)
begin
Frame.Init(Panel1);
Frame.Show;
end;
procedure form1.Button1Click(...)
begin
ShowFrame(fraUsers); // creates the frame if it does not exist
end;