Can't deserialize valid JSON using system.json - 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.

Related

Invoke a REST API (JSON) in PLSQL

I want to invoke a restful API (JSON) in plsql. The JSON Payload is as follows:
{
"data": {
"content": "encrypted content", "signature": "JKQWJK34K32JJEK2JQWJ5678",
"dataDescription": {
"codeType": "0",
"encryptCode": "1",
"zipCode": "0"
}
},
"globalInfo": {
"appId": "AP01",
"version": "1.1.20191201",
"dataExchangeId": "9230489223014123",
"interfaceCode": "T101",
"requestCode": "TP",
"requestTime": "2019-06-11 17:07:07",
"responseCode": "TA",
"userName": "admin",
"deviceMAC": "FFFFFFFFFFFF",
"deviceNo": "00022000634",
"tin": "1009830865",
5
"brn": "",
"taxpayerID": "1",
"longitude": "116.397128",
"latitude": "39.916527",
"extendField": {
"responseDateFormat": "dd/MM/yyyy",
"responseTimeFormat": "dd/MM/yyyy HH:mm:ss"
}
},
"returnStateInfo": {
"returnCode": "",
"returnMessage": ""
}
}
The Interface Codes (interfaceCode: T101) are defined, and for each method there is an interface Code, I just want to know how do i invoke a interface Method?
You can do it with UTL_HTTP package.
declare
v_req utl_http.req;
v_res utl_http.resp;
v_buffer varchar2(4000);
v_body varchar2(4000) := '{"field":"value"}'; -- Your JSON
begin
-- Set connection.
v_req := utl_http.begin_request('http://your.api/operation', 'POST');
utl_http.set_authentication(v_req, 'your_username','your_password');
utl_http.set_header(v_req, 'content-type', 'application/json');
utl_http.set_header(v_req, 'Content-Length', length(v_body));
-- Invoke REST API.
utl_http.write_text(v_req, v_body);
-- Get response.
v_res := utl_http.get_response(v_req);
begin
loop
utl_http.read_line(v_res, v_buffer);
-- Do something with buffer.
dbms_output.put_line(v_buffer);
end loop;
utl_http.end_response(v_res);
exception
when utl_http.end_of_body then
utl_http.end_response(v_res);
end;
end;
But if your database has got APEX installed, then you can try APEX_WEB_SERVICE package (much simpler).
declare
v_response clob;
v_buffer varchar2(32767);
v_buffer_size number := 32000;
v_offset number := 1;
begin
-- Set connection and invoke REST API.
v_response := apex_web_service.make_rest_request(
p_url => 'http://your.api/operation',
p_http_method => 'POST',
p_username => 'your_username',
p_password => 'your_password',
p_body => '{"field":"value"}' -- Your JSON.
);
-- Get response.
begin
loop
dbms_lob.read(v_response, v_buffer_size, v_offset, v_buffer);
-- Do something with buffer.
DBMS_OUTPUT.PUT_LINE(v_buffer);
v_offset := v_offset + v_buffer_size;
end loop;
exception
when no_data_found then
null;
end;
end;
If you get an ACL exception, then you have to create ACL to open TCP port to connect with REST API.
BEGIN
DBMS_NETWORK_ACL_ADMIN.create_acl (
acl => 'acl.xml',
description => 'Connecting with REST API',
principal => 'YOUR_DATABASE_SCHEMA',
is_grant => TRUE,
privilege => 'connect',
start_date => SYSTIMESTAMP,
end_date => NULL
);
DBMS_NETWORK_ACL_ADMIN.assign_acl (
acl => 'acl.xml',
host => 'localhost', -- Or hostname of REST API server (e.g. "example.com").
lower_port => 80, -- For HTTPS put 443.
upper_port => NULL
);
COMMIT;
end;
I assume your REST API is protected by basic authentication scheme (user and password). To keep this example simple i used HTTP. If you have to connect via HTTPS, then you have to change TCP port in ACL and configure Oracle Wallet for your Oracle database instance.

JSON returned by TIdHTTP cannot be decoded

I have the following code:
var
MemoryStream1: TMemoryStream;
IdHTTP1: TIdHTTP;
Bytes1: TBytes;
s1: string;
begin
IdHTTP1 := TIdHTTP.Create(nil);
MemoryStream1 := TMemoryStream.Create;
IdHTTP1.Get('https://restcountries.eu/rest/v2/all', MemoryStream1);
MemoryStream1.Position := 0;
MemoryStream1.SaveToFile('C:\Temp\MemoryStream1.txt');
MemoryStream1.Position := 0;
SetLength(Bytes1, MemoryStream1.Size);
MemoryStream1.Read(Bytes1[0], MemoryStream1.Size);
s1 := TEncoding.UTF8.GetString(Bytes1);
It fails at the last line with the message "no mapping for the unicode character in the target multi-byte code page exists"
However, I can see the returned JSON clearly in HttpAnalyzer.
The file 'C:\Temp\MemoryStream1.txt' starts with the hex '1F8B0800000000000003DC9BED6F1BB7' when I am expecting something corresponding to { - a left brace - as the first character
when I try
s1 := TEncoding.Unicode.GetString(Bytes1);
String s1 contains "Chinese" characters.
I worked it out. Apparently, the gzip is not decompressed. One must do it oneself. Here goes:
var
IdHTTP1: TIdHTTP;
a1: AnsiString;
s1: string;
MemoryStream1, MemoryStream2: TMemoryStream;
begin
Screen.Cursor := crHourGlass;
IdHTTP1 := TIdHTTP.Create(nil);
MemoryStream1 := TMemoryStream.Create;
MemoryStream2 := TMemoryStream.Create;
IdHTTP1.Get('https://restcountries.eu/rest/v2/all', MemoryStream1);
MemoryStream1.Position := 0;
IdCompressorZLib1.DecompressGZipStream(MemoryStream1, MemoryStream2);
FreeAndNil(MemoryStream1);
MemoryStream2.Position := 0;
SetLength(a1, MemoryStream2.Size);
MemoryStream2.Read(a1[1], MemoryStream2.Size);
// AnsiString a1 now contains the decompressed data

Delphi JSON superobject saving multiple objects to file in alphabetical order

I'm using the JSON superobject library to save objects to a file. When my code worked, they were written with proper formatting.
procedure TDCell.Save(fileName: string);
var i,j : integer;
JsonObjCol1, JsonObjCol2, JsonObjCol3, JsonObjCol4: ISuperObject;
begin
JsonArray := SO();
JsonObjCol1 := SO();
JsonObjCol2 := SO();
JsonObjCol3 := SO();
JsonObjCol4 := SO();
for i := 0 to Table.ColCount - 1 do
begin
for j := 0 to Table.RowCount - 1 do
begin
if Table.Objects[i, j] is TEdit then
with Table.Objects[i, j] as TEdit do
case i of
0:JsonArray[Name] := SO(Table.Cells[i, j]);
1:JsonObjCol1[Name] := SO(Table.Cells[i, j]);
2:JsonObjCol2[Name] := SO(Table.Cells[i, j]);
3:JsonObjCol3[Name] := SO(Table.Cells[i, j]);
4:JsonObjCol4[Name] := SO(Table.Cells[i, j]);
end
else
if Table.Objects[i, j] is TLabel then
with Table.Objects[i, j] as TLabel do
case i of
0:JsonArray[Name] := SO(Caption);
1:JsonObjCol1[Name] := SO(Caption);
2:JsonObjCol2[Name] := SO(Caption);
3:JsonObjCol3[Name] := SO(Caption);
4:JsonObjCol4[Name] := SO(Caption);
end
end;
case i of
0:JsonObject['Col' + IntToStr(Table.ColCount - 1 - i)] := JsonArray;
1:JsonObject['Col' + IntToStr(Table.ColCount - 1 - i)] := JsonObjCol1;
2:JsonObject['Col' + IntToStr(Table.ColCount - 1 - i)] := JsonObjCol2;
3:JsonObject['Col' + IntToStr(Table.ColCount - 1 - i)] := JsonObjCol3;
4:JsonObject['Col' + IntToStr(Table.ColCount - 1 - i)] := JsonObjCol4;
end;
end;
JsonObject.SaveTo(fileName, true);
end;
JsonArray also has type ISuperObject
But it seems that there is some opposite order sorting feature running in this library. Not only objects but also key-value pairs in them are written in opposite alphabetical order.
{
"Col4": {
"Label03": "Hello03",
"Label01": "Hello01",
"Edit04": "Hello04",
"Edit02": "Hello02",
"Edit00": "Hello00"
},
"Col3": {
"Label13": "Hello13",
"Label11": "Hello11",
"Edit14": "Hello14",
"Edit12": "Hello12",
"Edit10": "Hello10"
},
"Col2": {
"Label23": "Hello23",
"Label21": "Hello21",
"Edit24": "Hello24",
"Edit22": "Hello22",
"Edit20": "Hello20"
},
"Col1": {
"Label33": "Hello33",
"Label31": "Hello31",
"Edit34": "Hello34",
"Edit32": "Hello32",
"Edit30": "Hello30"
},
"Col0": {
"Label43": "Hello43",
"Label41": "Hello41",
"Edit44": "Hello44",
"Edit42": "Hello42",
"Edit40": "Hello40"
}
}
What should I do to make superobject write the file in the proper order? I've tested that objects are added in the proper order in cycles. The file must look like this.
{
"Col0": {
"Edit00": "Hello00",
"Label01": "Hello01",
"Edit02": "Hello02",
"Label03": "Hello03",
"Edit04": "Hello04"
},
"Col1": {
"Edit10": "Hello10",
"Label11": "Hello11",
"Edit12": "Hello12",
"Label13": "Hello13",
"Edit14": "Hello14"
},
and so on
There is no proper order. The elements of a JSON object can be presented in any order, and the JSON standard makes it clear that the meaning of the file cannot depend on the order of the elements of a JSON object:
An object is an unordered set of name/value pairs.
If you require ordered data then you need to use a JSON array:
An array is an ordered collection of values.

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"
},
.....
]
}

How to parse nested JSON object in Delphi XE2?

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;