Допустимый JSON парсинг с TJSONObject, используя пример Эмбаркадеро код выдает исключение

вот пример кода из справки 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 !!

используя парсить:

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

чтение файла успешно.

вот код, который вызывает проблему. Я использовал подход 2, как рекомендовано в документах Embarcadero, как указано выше. Это не удалось. Я разделил подход на несколько переменных для целей отладки.

согласно документам 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;

такого рода вещи, где нет адекватной документации для IDE и языка или где документация не является полной или адекватной - это ужасная трата времени и дает мне проблемы с завершением работы. Мне нужно решать проблемы с использованием языка и библиотек, а не решать проблемы с ними или более того с неадекватной, неоднозначной и труднодоступной документацией.

2 ответов


TJSONObject.ParseJSONValue() возвращает nil указатель при сбое синтаксического анализа. Пример Embarcadero не проверяет это условие. Если синтаксический анализ не удался, это объясняет ошибку "недопустимый тип приведения", вызванную as оператора.

TJSONObject.Parse() возвращает 1, Если разбор завершается неудачей. Пример Embarcadero не проверяет это условие.

, потому что TJSONObject разбирает байты, а не символы, я предлагаю вам не использовать TFile.ReadAllText(), который будет читать байты и преобразовывать их в UTF-16 используя TEncoding.Default если файл не имеет спецификации. В вашем конкретном примере это не проблема, так как ваш JSON содержит только символы ASCII. Но это может быть проблемой, если используются символы Unicode, отличные от ASCII. JSON по умолчанию использует UTF-8 (поэтому на TJSONArray будет добавлен как дочерний объект, который вы вызываете Parse() on, но это неназванный массив, поэтому вам нужно получить массив по индексу:

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;

код Реми корректен после следующих корректировок:

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;