使用Embarcadero代码示例使用TJSONObject解析有效JSON失败,并出现异常 [英] Parsing Valid JSON with TJSONObject using Embarcadero Code Example fails with exception

查看:849
本文介绍了使用Embarcadero代码示例使用TJSONObject解析有效JSON失败,并出现异常的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

以下是Embarcadero帮助中的示例代码( http://docwiki.embarcadero .com/RADStudio/Rio/en/JSON ):

Here is the example code from the Embarcadero help (http://docwiki.embarcadero.com/RADStudio/Rio/en/JSON):

您可以使用以下代码片段之一将JSON字符串表示形式转换为JSON.

you can transform the JSON string representation into a JSON with one of the following code snippets.

使用 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;

该方法失败,并且在as行上强制转换为类无效类型!

That approach fails with a class invalid type cast on the as line !!

使用解析:

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;

在Embarcadero示例中,输入JSON在代码中声明为字符串:

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"' +
    '}';

我正在处理的JSON来自BetFair.有效(已通过 http://jsonformatter.curiousconcept.com/

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);

文件读取成功.

这是导致问题的代码.我使用了Embarcadero文档中推荐的方法2,如上所示.那失败了.我将方法划分为更多变量以进行调试.

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.

根据Embarcadero文档,vParseResult将为负数 如果解析由于任何原因而失败.它不是.但是,即使解析成功(尝试后的第二行),vJSONPair最终也仍然为nil,这会导致异常:

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;

在没有足够的IDE和语言文档或文档不完整或不充分的情况下,这种事情很浪费时间,给我带来了完成工作的麻烦.我需要使用语言和库来解决问题,而不是用不足,模棱两可且难以找到文档的方式解决它们或更多的问题.

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()返回nil指针. Embarcadero的示例不检查这种情况.如果解析失败,则将说明as运算符引发的无效类型转换"错误.

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()返回-1. Embarcadero的示例不检查这种情况.

TJSONObject.Parse() returns -1 if parsing fails. Embarcadero's example does not check for that condition.

因为TJSONObject解析字节而不是字符,所以我建议您不要使用TFile.ReadAllText(),如果文件没有BOM,它将读取字节并将其使用TEncoding.Default解码为UTF-16.在您的特定示例中,这不是问题,因为您的JSON仅包含ASCII字符.但是,如果使用非ASCII Unicode字符,则可能会出现问题. JSON默认情况下使用UTF-8(这就是TJSONObject.ParseJSONValue()IsUTF8参数默认为true的原因.)

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).

无论如何,您的代码与显示的JSON数据的结构不匹配.您的JSON数据是一个对象数组,因此解析的第一项将是TJSONArray,而不是TJSONObject.如果使用TSJONObject.ParseJSONValue(),它将返回一个TJSONValue,可以将其类型转换为TJSONArray:

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;

或者简单地:

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;

如果改用TJSONObject.Parse(),则会将TJSONArray作为您正在调用Parse()的对象的子对象添加,但是它是未命名的数组,因此您必须按索引检索该数组:

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;

或者简单地:

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;

更新:如果您尝试使用 SuperObject ,则代码会更简单一些,例如:

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;

这篇关于使用Embarcadero代码示例使用TJSONObject解析有效JSON失败,并出现异常的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆