如何在Delphi中使用TJSONIterator 备份多级?

How to back up more than one level using TJSONIterator in Delphi?

我一直在尝试使用 Delphi 10.2.2 中的 TJSONIterator。简短的问题是 "How do I go up two levels in the Iterator?"

以下代码说明了我的问题:

JsonRec := '{"v1":"Main","v2":"1.1","v3":{"id":"X45","mod":1.5,"r2":{"rv1":"99190","rv2":"TX"}},"v4":"ok","v5":69}';

PDS.Open;
PDS.Append;
StringReader := TStringReader.Create(JsonRec);
JsonTextReader := TJsonTextReader.Create(StringReader);
Iterator := TJSONIterator.Create(JsonTextReader);
If Iterator.Next('v1') Then
   PDS['Type'] := Iterator.AsString;
If Iterator.Next('v2') Then
   PDS['Version'] := Iterator.AsString;
If Iterator.Next('v3') Then
   Begin
   Iterator.Recurse;
   If Iterator.Next('id') Then
      PDS['BlackListInfo'] := Iterator.AsString;
   If Iterator.Next('mod') Then
      PDS['Speed'] := Iterator.AsDouble;
   If Iterator.Next('r2') Then
      begin
      Iterator.Recurse;
      if Iterator.Next('rv1') then
         PDS['Serial'] := Iterator.AsString;
      if Iterator.Next('rv2') then
         PDS['Location'] := Iterator.AsString;
      Iterator.Return;
      end;
   Iterator.Return;   //Second Return does not go up a level.
   if Iterator.Next('v4') then // Always fails
      PDS['CRC'] := Iterator.AsString;
   if Iterator.Next('v5') then
      PDS['ReportID'] := Iterator.AsInteger;
   PDS.Post;
   End;

显然,我正在解析 JSON 字符串以将数据放入数据库 (PDS)。当我发出第二个 return 时,我没有达到预期的水平,然后我找不到 v4。我怀疑我可能需要使用 Rewind 方法,但到目前为止我一直无法找到它的文档。

非常感谢任何帮助。

简答

Iterator.Return 个调用之间使用 Iterator.Next。如果您不想在该级别处理任何内容,请清空。

简单替换这部分代码

  if Iterator.Next('rv2') then
     PDS['Location'] := Iterator.AsString;
  Iterator.Return;
  end;

有了这个

  if Iterator.Next('rv2') then
     PDS['Location'] := Iterator.AsString;
  Iterator.Return;
  Iterator.Next;
  end;

长答案

我不确定这是一个错误还是有意为之,文档也没有任何帮助,但 Return 仅适用于一个级别。如果你看一下实现,你会发现 Return 只会将 reader 移动到第一个结束标记,减少深度,并停留在那里,如果它还不在那里的话。

在这种情况下,Return的第一次调用移动到r2的末尾并减少深度,下一​​次调用将什么都不做,因为它已经在末尾并且not FReader.IsEndToken (FReader.TokenType) 条件不满足。

Iterator.Next('v4') 移动到 v3 的末尾并将深度减小到 1,但还将私有变量 FFinished 设置为 True,这将导致所有其他调用由于 if FFinished then Exit 开头的条件,Next 不会执行任何操作。重置 FFinish 的唯一方法是使用 ReturnRewind.

这个答案解释了如何实现你想做的事情,但不使用 TJsonReader,它是为 delphi 制作的最差 json 解析器(性能和可用性)之一。 (你可以用这个工具做一个基准:https://svn.code.sf.net/p/alcinoe/code/demos/ALJsonDoc/win32/AljsonDocDemo.exe

例如使用 Alcinoe (https://github.com/Zeus64/alcinoe) 代码非常简单(但任何其他 json 解析器也可以很好地完成这种工作)

MyJsonDoc := TalJsonDocumentU.create;
try
  MyJsonDoc.loadFromJsonString('{"v1":"Main","v2":"1.1","v3":{"id":"X45","mod":1.5,"r2":{"rv1":"99190","rv2":"TX"}},"v4":"ok","v5":69}');
  PDS['Type'] := MyJsonDoc.node.getchildNodeValueText('v1', ''{default});
  PDS['Version'] := MyJsonDoc.node.getchildNodeValueText('v2', ''{default});
  PDS['BlackListInfo'] := MyJsonDoc.node.getchildNodeValueText(['v3', 'id'], ''{default});
  PDS['Speed'] := MyJsonDoc.node.getchildNodeValueFloat(['v3', 'mod'], 0{default});
  PDS['Serial'] := MyJsonDoc.node.getchildNodeValueText(['v3', 'r2', 'rv1'], ''{default});
  PDS['Location'] := MyJsonDoc.node.getchildNodeValueText(['v3', 'r2', 'rv2'], ''{default});
  PDS['CRC'] := MyJsonDoc.node.getchildNodeValueText('v4', ''{default});
  PDS['ReportID'] := MyJsonDoc.node.getchildNodeValueInt32('v5', 0{default});
finally
  MyJsonDoc.free;
end;

我正在寻找有关如何使用 TJSONIterator 的示例,然后来到这里。由于几乎找不到任何东西,我想我会分享我想出的解决方案:

...
  ecDebug: TMemo;
...
uses
  System.JSON.Builders,
  System.JSON.Readers,
...
procedure TForm1.Button1Click(Sender: TObject);
const
  JsonRec = '{"v1":"Main","v2":"1.1","v3":{"id":"X45","mod":1.5,' +
    '"r2":{"rv1":"99190","rv2":"TX"}},"v4":"ok","v5":69}';
var
  StringReader: TStringReader;
  JsonTextReader: TJsonTextReader;
  Iterator: TJSONIterator;
begin
  JsonTextReader:= nil;
  Iterator:= nil;
  StringReader:= TStringReader.Create(JsonRec);
  try
    JsonTextReader:= TJsonTextReader.Create(StringReader);
    Iterator:= TJSONIterator.Create(JsonTextReader);
    while Iterator.Next do
    begin
      if Iterator.Key = 'v1' then
        ecDebug.Lines.Add(Format('Type = %s', [Iterator.AsString]))
      else if Iterator.Key = 'v2' then
        ecDebug.Lines.Add(Format('Version = %s', [Iterator.AsString]))
      else if Iterator.Key = 'v3' then
      begin
        Iterator.Recurse;
        while Iterator.Next do
        begin
           if Iterator.Key = 'id' then
             ecDebug.Lines.Add(Format('BlackListInfo = %s', [Iterator.AsString]))
           else if Iterator.Key = 'mod' then
             ecDebug.Lines.Add(Format('Speed = %g', [Iterator.AsDouble]))
           else if Iterator.Key = 'r2' then
           begin
             Iterator.Recurse;
             while Iterator.Next do
             begin
               if Iterator.Key = 'rv1' then
                 ecDebug.Lines.Add(Format('Serial = %s', [Iterator.AsString])) 
               else if Iterator.Key = 'rv2' then
                 ecDebug.Lines.Add(Format('Location = %s', [Iterator.AsString]));
             end;
             Iterator.Return;
           end;
        end;
        Iterator.Return;
      end
      else if Iterator.Key = 'v4' then
        ecDebug.Lines.Add(Format('CRC = %s', [Iterator.AsString]))
      else if Iterator.Key = 'v5' then
        ecDebug.Lines.Add(Format('ReportID = %d', [Iterator.AsInteger]));
    end;
  finally
    Iterator.Free;
    JsonTextReader.Free;
    StringReader.Free;
  end;
end;

我相信这就是 TJSONIterator 的使用方式。此代码不依赖于 JSON 元素的顺序。

备忘录中的输出是:

Type = Main
Version = 1.1
BlackListInfo = X45
Speed = 1,5
Serial = 99190
Location = TX
CRC = ok
ReportID = 69