【问题标题】:Rewinding input data of TJSONIterator class with TRewindReaderProc使用 TRewindReaderProc 倒带 TJSONIterator 类的输入数据
【发布时间】:2018-04-04 12:56:34
【问题描述】:

我正在尝试在使用 embarcadero 的 c++builder(Tokyo 10.2 Update 3)构建的程序中解析 JSON,考虑到他们严重缺乏文档,这并不容易。

我正在使用 TJSONIterator Find 方法,如果您提供的路径(例如 [0]['key'] 或 car.model['colour'])存在于 JSON 数据中,则返回 true 或 false,根据 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 类构造函数中,并被馈送到带有使用 _di_TRewindReaderProc 接口的类的 TJSONIterator 类构造函数中。

//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 方法应该进入什么?

【问题讨论】:

    标签: c++ json c++builder


    【解决方案1】:

    我在 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 的内部缓冲区,第二个将文件指针移动到开头。

    我希望这对您有所帮助,因为文档真的很差,而且我在 Internet 上找不到任何关于如何正确设置要传递给 JSON 迭代器的倒带过程的文章。

    PD:然后我发现与 TIterator.Find 一起工作时遇到了一些其他问题,但那是另一回事,所以我专注于你的具体问题。

    【讨论】:

      【解决方案2】:

      由于TJSONIterator 已经在阅读器上调用Rewind,在它调用您的 Rewind 过程之前,不需要再次调用 Rewind。而是重置流并丢弃阅读器的所有缓冲区:

      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:对于大于 1K 的 json 文件,需要解决 TJsonTextReader 中的错误,该错误无法清除 FChars,因此在调用 .Rewind 后不会重新读取 json 文件,这会导致异常“解析值时遇到意外字符..."。为了访问私有 FChars,我使用了https://stackoverflow.com/a/36717896/386473 中描述的 RTTI。该错误在 QP 中记录为https://quality.embarcadero.com/browse/RSP-24517。

      【讨论】:

        猜你喜欢
        • 2018-12-24
        • 1970-01-01
        • 2016-08-30
        • 1970-01-01
        • 2020-09-20
        • 2012-10-13
        • 2012-03-07
        • 2015-11-18
        • 2021-06-08
        相关资源
        最近更新 更多