使用Embarcadero代码解析使用TJSONObject的有效JSON示例失败,但有异常

时间:2014-07-18 00:26:37

标签: json delphi parsing

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

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

使用 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行上播放了类无效类型!!

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

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

  const
  GJSONString =
    '{' +
    '    "name": {'+
    '        "A JSON Object": {' +
    '          "id": "1"' +
    '        },' +
    '        "Another JSON Object": {' +
    '          "id": "2"' +
    '        }' +
    '    },' +
    '    "totalobjects": "2"' +
    '}';

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

我没有将此声明为字符串,而是从文件中读取它:

TFile.ReadAllText(aFileName);

文件读取成功。

以下是导致问题的代码。我按照Embarcadero docs的建议使用了方法2,如上所述。那失败了。为了调试目的,我将方法拆分为更多变量。

根据Embarcadero docs vParseResult将是否定的 如果解析因任何原因失败,则为value。它不是。但是,即使解析成功(尝试后的第二行),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;

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

2 个答案:

答案 0 :(得分:22)

如果解析失败,

TJSONObject.ParseJSONValue()将返回nil指针。 Embarcadero的例子没有检查这种情况。如果解析失败,那将解释"无效类型转换" as运算符引发错误。

如果解析失败,

TJSONObject.Parse()将返回-1。 Embarcadero的例子没有检查这种情况。

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

在任何情况下,您的代码都与您显示的JSON数据的结构不匹配。您的JSON数据是一个对象数组,因此解析的第一个项目将是TJSONArray,而不是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;

或者简单地说:

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()的对象的子项,但它是一个未命名的数组,因此您必须检索数组索引:

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,代码会更简单一些,例如:

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;

答案 1 :(得分:2)

在进行以下调整后,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;