I wrote the below code in Delphi 2010 to download a JSON string:
procedure TForm1.Button1Click(Sender: TObject);
var
strResult: string;
listParams: TStringList;
JO :TJSONObject;
JV : TJSONValue;
begin
listParams := TStringList.Create;
listParams.Add('action=GET');
listParams.Add('userid=(11,12,13)');
try
strResult := idhttp1.Post('http://xxxnet/api/users.php', listParams);
Memo1.Lines.Text:=strResult;
JO := TJSONObject.ParseJSONValue(TEncoding.ASCII.GetBytes(strResult), 0) as TJSONObject;
JV := JO.Get(0).JsonValue;
Memo2.Lines.Add(JV.Value);
finally
listParams.Free;
end;
end;
When the JSON contains a single object:
{"usertitle":"Mark","userid":"13","username":"950","useremail":"","success":"1","error":""}
The code works good.
But when the JSON contains multi objects:
[{"usertitle":"Yani","userid":"11","username":"887","useremail":"nili_orusoft#yahoo.com","success":"1","error":""},{"usertitle":"Frank","userid":"12","username":"851","useremail":"","success":"1","error":""},{"usertitle":"Mark","userid":"13","username":"950","useremail":"","success":"1","error":""}]
The code crashes with an "access violation at address 00522275" error.
There are two problems with your code:
You are leaking the object that ParseJSONValue() returns. You need to Free() it when you are done using it.
Your second JSON example is an array of objects. ParseJSONValue() will return a TJSONArray instead of a TJSONObject, so your as TJSONObject typecast will fail and raise an exception (but it should not be raising an access violation).
Try this code instead:
procedure TForm1.Button1Click(Sender: TObject);
var
strResult: string;
listParams: TStringList;
JA: TJSONArray;
JO: TJSONObject;
JV, JV2: TJSONValue;
begin
listParams := TStringList.Create;
try
listParams.Add('action=GET');
listParams.Add('userid=(11,12,13)');
strResult := idhttp1.Post('http://xxxnet/api/users.php', listParams);
finally
listParams.Free;
end;
Memo1.Lines.Text := strResult;
JV := TJSONObject.ParseJSONValue(TEncoding.UTF8.GetBytes(strResult), 0);
try
if JV is TJSONObject then
begin
JO := TJSONObject(JV);
JV2 := JO.Get(0).JsonValue;
Memo2.Lines.Add(JV2.Value);
end
else if JV is TJSONArray then
begin
JA := TJSONArray(JV);
JO := JA.Get(0) as TJSONObject;
JV2 := JO.Get(0).JsonValue;
Memo2.Lines.Add(JV2.Value);
end;
finally
JV.Free;
end;
end;
Related
This works, when it is an object:
{"data":{"url":"stackoverflow.com"}}
This does not work, when it is an array:
{"data":[{"url":"stackoverflow.com"}]}
Error: Invalid class typecast
procedure TForm1.Button1Click(Sender: TObject);
var
json : string;
obj, data : TJSONObject;
url : string;
begin
json := '{"data":{"url":"stackoverflow.com"}}';
obj := TJSonObject.ParseJSONValue(json) as TJSONObject;
try
data := obj.Values['data'] as TJSONObject;
url := data.Values['url'].value;
showMessage(url);
finally
obj.Free;
end;
end;
I know I have to use TJSONArray, but I don't know how to implement it.
This is not hard to understand.
In the first case, the value of data is an object, so obj.Values['data'] as TJSONObject is correct.
In the second case, the value of data is an array, so obj.Values['data'] as TJSONObject is wrong. it needs to be obj.Values['data'] as TJSONArray instead, and then you access the TJSONObject from the elements of the array, eg:
procedure TForm1.Button1Click(Sender: TObject);
var
json : string;
obj, data : TJSONObject;
arr: TJSONArray;
url : string;
begin
json := '{"data":[{"url":"stackoverflow.com"}]}';
obj := TJSonObject.ParseJSONValue(json) as TJSONObject;
try
arr := obj.Values['data'] as TJSONArray;
data := arr[0] as TJSONObject;
url := data.Values['url'].Value';
ShowMessage(url);
finally
obj.Free;
end;
end;
How do I save a physical .json file to my C: \ drive starting from my JSONObject3 object?
procedure CreateJSON;
var
JSONObject2, JSONObject3: TJSONObject;
JSONValue1, JSONValue2: TJSONValue;
JSONArray: TJSONArray;
JSONString1, JSONString2: TJSONString;
AText, BText: string;
mStringStream: TStringStream;
begin
JSONObject2 := TJSONObject.Create;
JSONObject3 := TJSONObject.Create;
JSONArray := TJSONArray.Create;
try
AText := 'Name';
BText := '"Charles"';
JSONString2 := TJSONString.Create(AText);
JSONValue2 := TJSONObject.ParseJSONValue(BText);
JSONObject2.AddPair(JSONString2, JSONValue2);
JSONArray.Add(JSONObject2);
JSONObject3.AddPair('People', JSONArray);
mStringStream := TStringStream.Create('', TEncoding.UTF8);
// m_StringStream.LoadFromStream(JSONObject3.ToString); <---ERROR
mStringStream.SaveToFile('people.json');
finally
JSONObject3.Free;
end;
end;
Thank you, I am a beginner with the json topic
TJSONObject does not have any streaming support, but it does have several To...() output methods (ToBytes(), ToJSON() ToString()). The output of any of those methods can be written to a file, such as with TFile.WriteAll...() methods (WriteAllBytes(), WriteAllText()).
Try this instead:
uses
...,
Data.DBXJSON, // use System.JSON in XE6+
System.IOUtils,
System.SysUtils;
procedure CreateJSON;
var
JSONObject, JSONObject2: TJSONObject;
JSONValue: TJSONValue;
JSONArray: TJSONArray;
AText, BText: string;
begin
JSONObject := TJSONObject.Create;
try
AText := 'Name';
BText := '"Charles"';
JSONValue := TJSONObject.ParseJSONValue(BText);
if JSONValue <> nil then
try
JSONObject.AddPair(AText, JSONValue);
except
JSONValue.Free;
raise;
end;
JSONArray := TJSONArray.Create;
try
JSONObject2 := TJSONObject.Create;
try
JSONArray.Add(JSONObject2);
except
JSONObject2.Free;
raise;
end;
JSONObject.AddPair('People', JSONArray);
except
JSONArray.Free;
raise;
end;
TFile.WriteAllText('people.json', JSONObject.ToJSON, TEncoding.UTF8);
finally
JSONObject.Free;
end;
end;
I have a JSON string which comes from Amazon. (https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/index.json) The top of the JSON looks like this...
I need to retrieve a list of JUST the offerCode and versionIndexUrl values. I can read an array of offers, but the key of each offer is different, so I can't use the NAME(comprehend, AmazonMWAA, etc). I have tried using element [0] but I get an AV. Here is my relevant code...
procedure Load_AWS_Services;
var
json: string;
idx: Integer;
obj: TJSONObject;
j_array: TJSONArray; // the array of all lineitems/offers
lineItem : TJSONObject;
ServiceEntry: TJSONPair;
begin
try
JSON := DownloadFromURLasString('https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/index.json');
obj := TJSONObject.ParseJSONValue(JSON) as TJSONObject;
try
// Now parse the data...
j_array := TJSONArray(obj.Get('offers').JsonValue);
// Now loop through each individual item
for idx := 0 to pred(j_array.size) do
begin
lineItem := TJSONObject(j_array.Get(idx));
Main.Memo1.Lines.Add(lineItem.ToString); // this shows each offer...so good to this point
ServiceEntry := lineItem.Pairs[0];
ShowMessage(ServiceEntry.Value); // AV here
What do I need to change in my last 2 lines to read INSIDE the 'offers'?
The offers field is a JSON object, not a JSON array (had you used the as operator for the TJSONArray cast, you would have gotten an EInvalidCast exception raised).
Try this instead:
procedure Load_AWS_Services;
var
json: string;
idx: Integer;
j_val: TJSONValue;
j_obj: TJSONObject;
j_pair: TJSONPair;
offers: TJSONObject;
lineItem : TJSONObject;
begin
try
json := DownloadFromURLasString('https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/index.json');
j_val := TJSONObject.ParseJSONValue(json);
try
j_obj := j_val as TJSONObject;
// Now parse the data...
offers := j_obj.GetValue('offers') as TJSONObject;
// Now loop through each individual item
for idx := 0 to pred(offers.Count) do
begin
j_pair := offers.Get(idx);
Main.Memo1.Lines.Add(j_pair.JsonString.Value);
lineItem := j_pair.JsonValue as TJSONObject;
ShowMessage(lineItem.GetValue('offerCode').Value);
ShowMessage(lineItem.GetValue('versionIndexUrl').Value);
...
end;
finally
j_val.Free;
end;
except
...
end;
end;
I have a fairly simple JSON:
[
{
"hero-img": "girl-ipad.png",
"main-story-headline": "How to Stay Mentally Healthy During a Pandemic",
"main-story-paragraph": "lorem ipsum",
"main-story-button-text": "Read More"
},
{
"hero-img": "painter-woman.png",
"main-story-headline": "How to Stay Mentally Healthy During a Pandemic",
"main-story-paragraph": "lorem ipsum",
"main-story-button-text": "Explore More"
},
{
"hero-img": "old-man.png",
"main-story-headline": "How to Stay Mentally Healthy During a Pandemic",
"main-story-paragraph": "lorem ipsum",
"main-story-button-text": "Get Yours Now"
} ]
By using Delphi 10.3 Rio, I want to iterate through all properties no matter how they are named. I've started this way:
program JuTemplates;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, System.JSON, System.IOUtils, System.Types;
var
root, OutputDir, TemplatesDir, JsonDir, jsonText: string;
JsonFiles: TStringDynArray;
i, j: Integer;
JSONValue: TJSONObject;
JSONValues: TJSONArray;
begin
try
try
root := ExtractFilePath(ParamStr(0));
OutputDir := root + 'OutputDir\';
TemplatesDir := root + 'TemplatesDir\';
JsonDir := root + 'JsonDir\';
writeln('Processing: ' + JsonDir);
JsonFiles := TDirectory.GetFiles(JsonDir);
for i := 0 to High(JsonFiles) do
begin
jsonText := TFILE.ReadAllText(JsonFiles[i]);
JSONValues := TJSONObject.ParseJSONValue(jsonText) as TJSONArray;
for j := 0 to JSONValues.Count - 1 do
begin
JSONValue := JSONValues.Items[i] as TJSONObject;
// here I should iterate through that JSONValue object
end;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
finally
writeln ('Press any key to continue');
readln;
end;
end.
To enumerate a JSONObject, you can use an enumerator like below.
Use for..in loop (implicit enumerator)
procedure TForm1.Button1Click(Sender: TObject);
var
JSONData : String;
JSONObject : TJSONObject;
JSONPair : TJSONPair;
begin
JSONData := '... some JSON data ...'; // Place you data here
JSONObject := TJSonObject.ParseJSONValue(JSONData) as TJSONObject;
try
for JSONPair in JSONObject do
ShowMessage(Format('1) Key=%s Value=%s',
[JSONPair.JsonString.ToString,
JSONPair.JsonValue.ToString]));
finally
JSONObject.Free;
end;
end;
Use a classic for loop:
procedure TForm1.Button1Click(Sender: TObject);
var
JSONData : String;
JSONObject : TJSONObject;
JSONPair : TJSONPair;
I : Integer;
begin
JSONData := '... some JSON data ...'; // Place you data here
JSONObject := TJSonObject.ParseJSONValue(JSONData) as TJSONObject;
try
for I := 0 to JSONObject.Count - 1 do begin
ShowMessage(Format('Key=%s Value=%s',
[JSONObject.Pairs[I].JsonString.ToString,
JSONObject.Pairs[I].JsonValue.ToString]));
end;
finally
JSONObject.Free;
end;
end;
Use an explicit enumerator and a while loop:
procedure TForm1.Button1Click(Sender: TObject);
var
JSONData : String;
JSONObject : TJSONObject;
JSONEnumerator : TJSONObject.TEnumerator;
JSONPair : TJSONPair;
begin
JSONData := '... some JSON data ...'; // Place you data here
JSONObject := TJSonObject.ParseJSONValue(JSONData) as TJSONObject;
try
JSONEnumerator := JSONObject.GetEnumerator;
try
while JSONEnumerator.MoveNext do begin
JSONPair := JSONEnumerator.Current;
ShowMessage(Format('Key=%s Value=%s',
[JSONPair.JsonString.ToString,
JSONPair.JsonValue.ToString]));
end;
finally
JSONEnumerator.Free;
end;
finally
JSONObject.Free;
end;
end;
Note that you may need to check if JSONPair.JsonValue has childs and enumerate those childs with another enumerator, recursively.
I have a TDataset JSON helper function.
I want to result return the following:
{"result":[{"rid":"2","firstname":"veli","lastname":"deli"},{"rid":"1","firstname":"ismail","lastname":"kocacan"}]}
But.Does not work.The return the following:
{"result":[[{"rid":"2","firstname":"veli","lastname":"deli"},{"rid":"1","firstname":"ismail","lastname":"kocacan"}]]}
I want to remove extra [ character...
How to extract this data and return ?
{"rid":"2","firstname":"veli","lastname":"deli"},{"rid":"1","firstname":"ismail","lastname":"kocacan"}
My TDataset JSON Helper function :
TDatasetJSONHelper = class helper for TDataset
public
function ToJSONData: TJSONArray;
function ToJSONDataWrapper: TJSONValue;
end;
function TDatasetJSONHelper.ToJSONData: TJSONArray;
var
jso: TJSONObject;
jsa: TJSONArray;
jsp: TJsonPair;
J: Integer;
avalue: TJSONValue;
begin
Self.Close;
Self.Open;
jsa := TJSONArray.Create();
while not Self.Eof do
begin
jso := TJSONObject.Create();
for J := 0 to FieldCount - 1 do
jso.AddPair(TJsonPair.Create(Fields[J].DisplayName, Fields[J].Value));
jsa.AddElement(jso);
Self.Next;
end;
Self.Close;
Result := jsa;
end;
function TDatasetJSONHelper.ToJSONDataWrapper: TJSONValue;
var
aJSON: TJSONObject;
apair: TJsonPair;
avalue: TJSONValue;
begin
aJSON := TJSONObject.ParseJSONValue
(TEncoding.ASCII.GetBytes
('{"result":[[{"rid":"2","firstname":"veli","lastname":"deli"},{"rid":"1","firstname":"ismail","lastname":"kocacan"}]]}'),
0) as TJSONObject;
apair := aJSON.Get(0);
avalue := apair.JsonValue;
// ??? avalue := '{"rid":"2","firstname":"veli","lastname":"deli"},{"rid":"1","firstname":"ismail","lastname":"kocacan"}';
Result := avalue;
end;
function TServerMethods1.GetPersonList: TJSONArray;
begin
Result := dm.AdoQuery1.ToJSONData;
end;
function TServerMethods1.GetPersonList2: TJSONValue;
begin
Result := dm.AdoQuery1.ToJSONDataWrapper;
end;
How can I solve this problem ?
GetInvocationMetadata().ResponseCode := 200;
GetInvocationMetadata().ResponseContent := '{"rid":"2","firstname":"veli","lastname":"deli"},{"rid":"1","firstname":"ismail","lastname":"kocacan"}';
FYI, we added a more complete and faster function, in our Open Source repository.
It is part of our mORMot framework, but can be used as a stand-alone unit, not tied to other features.
See in SynVirtualDataSet.pas:
function DataSetToJSON(Data: TDataSet): RawUTF8
See this commit and the associated forum thread.