Перемотка входных данных класса TJSONIterator с помощью TRewindReaderProc

Я пытаюсь разобрать JSON в программе, созданной с помощью сборщика C++ от embarcadero (Tokyo 10.2 Update 3), что непросто, учитывая серьезное отсутствие документации.

Я использую метод TJSONIteratorFind, который возвращает true или false, если указанный вами путь (например, [0]['key'] или car.model['colour']) существует в данных JSON, которые, согласно документации embarcadero, требуют процедуры перемотки, переданной конструктору класса TJSONIterator, и если это не так тогда возникает исключение, в котором говорится об этом.

Процедура перемотки должна унаследовать интерфейс _di_TRewindReaderProc, так что вот мой класс.

class rewindclass : public TJSONIterator::_di_TRewindReaderProc
{
    public:
    void __fastcall Invoke(System::Json::Readers::TJsonReader* AReader)
    {
        //code to rewind Iterator
        Areader->Rewind();
    }
};

Я не уверен, что должно входить в функцию Invoke, потому что, как я уже сказал, документация бесполезна. Очевидно, вам нужно что-то сделать с переданным TJsonReader, и единственная функция, которую я вижу, которую можно использовать, - это Rewind, но я не думаю, что это так, потому что единственное, что в документации говорится о TRewindReaderProc, это

Reference to a procedure that rewinds the input data of the specified JSON reader.

Note: TJsonReader.Rewind does not rewind the input data, it resets the state of the JSON 
reader. This procedure must rewind the actual data stream that provides the input data 
of the JSON reader.

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

Я использую TStringReader для чтения данных JSON, которые передаются в конструктор класса TJsonTextReader и передаются в конструктор класса TJSONIterator с классом, использующим интерфейс _di_TRewindReaderProc.

//create rewindclass
rewindclass *rewind = new rewindclass();

//setting up TJSONIterator class
TStringReader *sread = new TStringReader(this->Memo1->Text);
TJsonTextReader *jread = new TJsonTextReader(sread);
TJSONIterator *jit = new TJSONIterator(jread, *rewind);

Этот код компилируется нормально, но когда я отлаживаю его и перехожу в конструктор TJSONIterator, TJsonTextReader не проходит, и из-за этого, когда я вызываю метод Find во второй раз, он выдает исключение, в котором говорится, что процедура обратного вызова не установлена.

Итак, кто-нибудь знает, почему _di_TRewindReaderProc не проходит и что должно входить в метод Invoke?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
277
2

Ответы 2

Я часами боролся с той же проблемой в Delphi, и, наконец, мне удалось найти способ заставить ее работать. Я использовал TFileStream и TStreamReader в качестве основных читателей вместо TStringReader, но я надеюсь, что вы понимаете концепции, лежащие в основе любого читателя. Это мой код:

procedure LoadFromFile(AFileName: string);
var
  FS: TFileStream;
  StreamReader: TStreamReader;
  JTReader: TJsonTextReader;
  JIterator: TJSONIterator;

  RewindProc: TJSONIterator.TRewindReaderProc;

begin
  RewindProc := procedure (AReader: TJsonReader)
  begin
    TStreamReader(TJsonTextReader(AReader).Reader).DiscardBufferedData;
    TStreamReader(TJsonTextReader(AReader).Reader).BaseStream.Seek(0, TSeekOrigin.soBeginning);
  end;

  FS:=TFileStream.Create(AFileName, fmOpenRead);
  StreamReader:=TStreamReader.Create(FS);
  JTReader:=TJsonTextReader.Create(StreamReader);
  JIterator:=TJSONIterator.Create(JTReader , RewindProc);

  JIterator.Find('some.path.here');
  JIterator.Find('other.path.here');

end;

Строки внутри процедуры RewindProc творят волшебство. Как сказано в документации, вы должны выполнить перемотку основных читателей, и это то, что я сделал с этими двумя строками. Первый очищает внутренний буфер StreamReader, а второй перемещает указатель файла в начало.

Я надеюсь, что это поможет вам в какой-то мере, потому что документация действительно скудная, и я не могу найти в Интернете статью о том, как правильно настроить процедуру перемотки, которая будет передаваться итератору JSON.

PD: Затем я обнаружил некоторые другие проблемы при работе с TIterator.Find, но это другое дело, поэтому я сосредоточился на вашем конкретном вопросе.

Поскольку TJSONIterator уже вызывает Rewind на считывателе, перед тем, как он вызовет вашу процедуру перемотки, повторный вызов перемотки не требуется. Вместо этого сбросьте поток и сбросьте все буферы Reader:

procedure TForm1.Button1Click(Sender: TObject);
const
  JsonRec = '{"some":{"path":{"there":"ahi", "here":"acqui"}}}';
var
  StringStream: TStringStream;
  StreamReader: TStreamReader;
  JsonTextReader: TJsonTextReader;
  Iterator: TJSONIterator;
begin
  JsonTextReader:= nil;
  Iterator:= nil;
  StringStream:= TStringStream.Create(JsonRec);
  try
    StreamReader:= TStreamReader.Create(StringStream);
    JsonTextReader:= TJsonTextReader.Create(StreamReader);
    Iterator:= TJSONIterator.Create(JsonTextReader,
      procedure (AReader: TJSONReader)
      var
        v: TValue;
      begin
        StringStream.Seek(0, soBeginning);
        StreamReader.DiscardBufferedData;
        //workaround for RSP-24517
        v:= TRttiContext.Create.GetType(TJsonTextReader).GetField('FChars').GetValue(AReader);
        v.SetArrayElement(0, #0);
      end);
    if Iterator.Find('some.path.here') then
      ecDebug.Lines.Add(Iterator.AsString);
    if Iterator.Find('some.path.there') then
      ecDebug.Lines.Add(Iterator.AsString);
  finally
    Iterator.Free;
    JsonTextReader.Free;
    StreamReader.Free;
    StringStream.Free;
  end;
end;

Однако, похоже, нет способа сбросить TStringReader, поэтому вместо этого я использую TStringStream.

Обновление: я добавил необходимый вызов DiscardBufferedData в процедуре перемотки. Это обнаружилось только после тестов с большими файлами.

Update2: с файлами json размером более 1 КБ требуется обходной путь для ошибки в TJsonTextReader, которая не может очистить FChars, поэтому он не перечитывает файл json после вызова .Rewind, что вызывает исключение «Обнаружен неожиданный символ. при разборе значения ... ". Для доступа к частным FChars я использую RTTI, как описано в https://stackoverflow.com/a/36717896/386473. Ошибка регистрируется в QP как https://quality.embarcadero.com/browse/RSP-24517.

Другие вопросы по теме