使用 Embarcadero 代码示例使用 TJSONObject 解析有效 JSON 失败并出现异常

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/24815625/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 17:25:45  来源:igfitidea点击:

Parsing Valid JSON with TJSONObject using Embarcadero Code Example fails with exception

jsondelphiparsing

提问by Bruce Long

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

以下是 Embarcadero 帮助中的示例代码 ( 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.

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

Using ParseJSONValue:

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

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

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:

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

  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.htmland http://jsonlint.com/):

我正在处理的 JSON 来自 BetFair。它是有效的(通过http://jsonformatter.curiousconcept.com/http://www.freeformatter.com/json-validator.htmlhttp://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.

这是导致问题的代码。我使用了上面一行中 Embarcadero 文档中推荐的方法 2。那失败了。出于调试目的,我将方法拆分为更多变量。

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:

根据 Embarcadero 文档,如果解析因任何原因失败,vParseResult 将为负值。它不是。但是,即使解析成功(尝试后的第二行),vJSONPair 最终还是为零,这会导致异常:

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.

这种没有足够的 IDE 和语言文档,或者文档不完整或不充分的事情 - 是一种可怕的时间浪费,给我完成工作带来了问题。我需要使用语言和库来解决问题,而不是用它们解决问题,或者更多地解决不充分、模棱两可且难以找到的文档。

回答by Remy Lebeau

TJSONObject.ParseJSONValue()returns a nilpointer 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 asoperator.

TJSONObject.ParseJSONValue()nil如果解析失败,则返回一个指针。Embarcadero 的示例不检查该条件。如果解析失败,这将解释as操作员引发的“无效类型转换”错误。

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

TJSONObject.Parse()如果解析失败,则返回 -1。Embarcadero 的示例不检查该条件。

Because TJSONObjectparses bytes, not characters, I suggest you not use TFile.ReadAllText(), which will read bytes and decode them to UTF-16 using TEncoding.Defaultif 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 IsUTF8parameter of TJSONObject.ParseJSONValue()is true by default).

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

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 TJSONValuethat can be type-casted to TJSONArray:

在任何情况下,您的代码都与您显示的 JSON 数据的结构不匹配。您的 JSON 数据是一个对象数组,因此解析的第一项将是 a TJSONArray,而不是 a TJSONObject。如果您使用TSJONObject.ParseJSONValue(),它将返回一个TJSONValue可以类型转换为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 TJSONArraywill 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:

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

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 SuperObjectinstead, the code would be a little simpler, eg:

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

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;

回答by Bruce Long

Remy's code is correct after the following adjustments:

Remy的代码经过以下调整后是正确的:

var
  vJSONBytes: TBytes;
  vJSONScenario: TJSONValue;
  vJSONArray: TJSONArray;
  vJSONValue: TJSONValue;
  vJSONObject: TJSONObject;
  vJSONPair: TJSONPair;
  vJSONScenarioEntry: TJSONString;
  vJSONScenarioValue: TJSONValue;
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(pScenarioKey);
      vJSONScenarioEntry := vJSONPair.JsonString;
      //vJSONScenarioValue := vJSONScenarioEntry.Value;
      vJSONScenarioValue := vJSONPair.JsonValue;
      cbScenario.Items.Add(vJSONScenarioValue.ToString);
    end;
  finally
    vJSONScenario.Free;
  end;
end;