Here is the example code from the Embarcadero help (http://docwiki.embarcadero.com/RADStudio/Rio/en/JSON):
you can transform the JSON string representation into a JSON with one of the following code snippets.
Using ParseJSONValue:
procedure ConsumeJsonString;
var
LJSONObject: TJSONObject;
begin
LJSONObject := nil;
try
{ convert String to JSON }
LJSONObject := TJSONObject.ParseJSONValue(TEncoding.ASCII.GetBytes(GJSONString), 0) as TJSONObject;
{ output the JSON to console as String }
Writeln(LJSONObject.ToString);
finally
LJSONObject.Free;
end;
That approach fails with a class invalid type cast on the as line !!
Using Parse:
procedure ConsumeJsonBytes;
var
LJSONObject: TJSONObject;
begin
LJSONObject := nil;
try
LJSONObject := TJsonObject.Create;
{ convert String to JSON }
LJSONObject.Parse(BytesOf(GJSONString), 0);
{ output the JSON to console as String }
Writeln(LJSONObject.ToString);
finally
LJSONObject.Free;
end;
end;
In the Embarcadero example The input JSON is declared in code as a string:
const
GJSONString =
'{' +
' "name": {'+
' "A JSON Object": {' +
' "id": "1"' +
' },' +
' "Another JSON Object": {' +
' "id": "2"' +
' }' +
' },' +
' "totalobjects": "2"' +
'}';
The JSON I am processing is from BetFair. It is valid (verified with http://jsonformatter.curiousconcept.com/ and http://www.freeformatter.com/json-validator.html and http://jsonlint.com/):
[{
"caption": "Get the number of soccer markets",
"methodName": "SportsAPING/v1.0/listEventTypes",
"params": {
"filter": {
"eventTypeIds": [
1
]
}
}
},
{
"caption": "Get the next horse race in the UK",
"methodName": "SportsAPING/v1.0/listMarketCatalogue",
"params": {
"filter": {
"eventTypeIds": [
7
],
"marketCountries": [
"GB"
],
"marketTypeCodes": [
"WIN"
],
"marketStartTime": {
"from": "2013-04-11T11:03:36Z"
}
},
"sort": "FIRST_TO_START",
"maxResults": "1",
"marketProjection": [
"COMPETITION",
"EVENT",
"EVENT_TYPE",
"MARKET_DESCRIPTION",
"RUNNER_DESCRIPTION"
]
}
},
{
"caption": "Get the 2 best prices, rolled up to £10 for the London Mayor Election 2016",
"methodName": "SportsAPING/v1.0/listMarketBook",
"params": {
"marketIds": [
"1.107728324"
],
"priceProjection": {
"priceData": [
"EX_BEST_OFFERS"
],
"exBestOffersOverrides": {
"bestPricesDepth": "2",
"rollupModel": "STAKE",
"rollupLimit": "10"
}
}
}
},
{
"caption": "Get my current unmatched bets",
"methodName": "SportsAPING/v1.0/listCurrentOrders",
"params": {
"orderProjection": "EXECUTABLE"
}
},
{
"caption": "Get my application keys",
"methodName": "AccountAPING/v1.0/getDeveloperAppKeys",
"params": {
}
}]
I am not declaring this as a string, but reading it from file thus:
TFile.ReadAllText(aFileName);
The file read is successfully.
Here is the code that causes the problem. I have used approach 2 as recommended from Embarcadero docs as in line above. That failed. I split the approach across more variables for debugging purposes.
According to the Embarcadero docs vParseResult will be a negative value if parsing fails for any reason. It does not. However, vJSONPair ends up nil even though the parse succeeds (second line after the try) which leads to an exception:
procedure TfMain.loadScenarioData(aFilename: string);
var
vJSONString: string;
vJSONScenario: TJSONObject;
vJSONPair: TJSONPair;
vJSONScenarioEntry: TJSONValue;
vJSONScenarioValue: string;
I: Int16;
vParseResult: Integer;
begin
vJSONString := TFile.ReadAllText(aFileName);
vJSONScenario := nil;
try
vJSONScenario := TJSONObject.Create;
vParseResult := vJSONScenario.Parse(BytesOf(vJSONString),0);
if vParseResult >= 0 then
begin
//BetFair Specific 'caption' key
vJSONPair := vJSONScenario.Get('caption');
vJSONScenarioEntry := vJSONPair.JsonValue;
vJSONScenarioValue := vJSONScenarioEntry.Value;
cbScenario.Items.Add(vJSONScenarioValue);
end;
finally
vJSONScenario.Free;
end;
end;
This kind of thing where there is not adequate documentation for the IDE and language or where the documentation is not complete or adequate - is a terrible waste of time and gives me problems with completing work. I need to be solving problems using the language and libraries, not solving problems with them or more to the point with inadequate, ambiguous, and hard to find documentation.
TJSONObject.ParseJSONValue()
returns a nil
pointer if parsing fails. Embarcadero's example does not check for that condition. If parsing failed, that would account for the "invalid type cast" error being raised by the as
operator.
TJSONObject.Parse()
returns -1 if parsing fails. Embarcadero's example does not check for that condition.
Because TJSONObject
parses bytes, not characters, I suggest you not use TFile.ReadAllText()
, which will read bytes and decode them to UTF-16 using TEncoding.Default
if the file does not have have a BOM. In your particular example, that is not an issue since your JSON contains only ASCII characters. But that can be an issue if non-ASCII Unicode characters are used. JSON uses UTF-8 by default (which is why the IsUTF8
parameter of TJSONObject.ParseJSONValue()
is true by default).
In any case, your code does not match the structure of the JSON data you have shown. Your JSON data is an array of objects, so the first item parsed will be a TJSONArray
, not a TJSONObject
. If you use TSJONObject.ParseJSONValue()
, it will return a TJSONValue
that can be type-casted to TJSONArray
:
procedure TfMain.loadScenarioData(aFilename: string);
var
vJSONBytes: TBytes;
vJSONScenario: TJSONValue;
vJSONArray: TJSONArray;
vJSONValue: TJSONValue;
vJSONObject: TJSONObject;
vJSONPair: TJSONPair;
vJSONScenarioEntry: TJSONValue;
vJSONScenarioValue: TJSONString;
begin
vJSONBytes := TFile.ReadAllBytes(aFileName);
vJSONScenario := TJSONObject.ParseJSONValue(vJSONBytes, 0);
if vJSONScenario <> nil then
try
//BetFair Specific 'caption' key
vJSONArray := vJSONScenario as TJSONArray;
for vJSONValue in vJSONArray do
begin
vJSONObject := vJSONValue as TJSONObject;
vJSONPair := vJSONObject.Get('caption');
vJSONScenarioEntry := vJSONPair.JsonValue;
vJSONScenarioValue := vJSONScenarioEntry as TJSONString;
cbScenario.Items.Add(vJSONScenarioValue.Value);
end;
finally
vJSONScenario.Free;
end;
end;
Or simply:
procedure TfMain.loadScenarioData(aFilename: string);
var
vJSONScenario: TJSONValue;
vJSONValue: TJSONValue;
begin
vJSONScenario := TJSONObject.ParseJSONValue(TFile.ReadAllBytes(aFileName), 0);
if vJSONScenario <> nil then
try
//BetFair Specific 'caption' key
for vJSONValue in vJSONScenario as TJSONArray do
begin
cbScenario.Items.Add(((vJSONValue as TJSONObject).Get('caption').JsonValue as TJSONString).Value);
end;
finally
vJSONScenario.Free;
end;
end;
If you use TJSONObject.Parse()
instead, the TJSONArray
will get added as a child of the object you are calling Parse()
on, but it is an unnamed array so you have to retrieve the array by index:
procedure TfMain.loadScenarioData(aFilename: string);
var
vJSONBytes: TBytes;
vJSONScenario: TJSONObject;
vJSONArray: TJSONArray;
vJSONValue: TJSONValue;
vJSONObject: TJSONObject;
vJSONPair: TJSONPair;
vJSONScenarioEntry: TJSONString;
vJSONScenarioValue: string;
vParseResult: Integer;
begin
vJSONBytes := TFile.ReadAllBytes(aFileName);
vJSONScenario := TJSONObject.Create;
try
vParseResult := vJSONScenario.Parse(vJSONBytes, 0);
if vParseResult >= 0 then
begin
//BetFair Specific 'caption' key
vJSONArray := vJSONScenario.Get(0) as TJSONArray;
for vJSONValue in vJSONArray do
begin
vJSONObject := vJSONValue as TJSONObject;
vJSONPair := vJSONObject.Get('caption');
vJSONScenarioEntry := vJSONPair.JsonString;
vJSONScenarioValue := vJSONScenarioEntry.Value;
cbScenario.Items.Add(vJSONScenarioValue);
end;
end;
finally
vJSONScenario.Free;
end;
end;
Or simply:
procedure TfMain.loadScenarioData(aFilename: string);
var
vJSONScenario: TJSONObject;
vJSONValue: TJSONValue;
vParseResult: Integer;
begin
vJSONScenario := TJSONObject.Create;
try
vParseResult := vJSONScenario.Parse(TFile.ReadAllBytes(aFileName), 0);
if vParseResult >= 0 then
begin
//BetFair Specific 'caption' key
for vJSONValue in vJSONScenario.Get(0) as TJSONArray do
begin
cbScenario.Items.Add(((vJSONValue as TJSONObject).Get('caption').JsonValue as TJSONString).Value);
end;
end;
finally
vJSONScenario.Free;
end;
end;
Update: If you try SuperObject instead, the code would be a little simpler, eg:
procedure TfMain.loadScenarioData(aFilename: string);
var
vJSONScenario: ISuperObject;
vJSONArray: ISuperObject;
vJSONObject: ISuperObject;
vJSONScenarioValue: string;
I: Integer;
begin
vJSONScenario := TSuperObject.ParseFile(aFileName);
//BetFair Specific 'caption' key
vJSONArray := vJSONScenario.AsArray;
for I := 0 to vJSONArray.Length-1 do
begin
vJSONObject := vJSONArray[I].AsObject;
vJSONScenarioValue := vJSONObject.S['caption'];
cbScenario.Items.Add(vJSONScenarioValue);
end;
end;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With