将JSON解析为TListBox

8

大家晚上好!

我目前正在尝试为桌面编写一个 CloudFlare 客户端。我已连接到他们的 API,并成功通过 POST 请求检索了 JSON 结果(结果已输出到 TMemo)。现在我想将这些结果解析到一个 TListBox 中(请参见粗体区域的示例)。该项目是使用 Firemonkey 设计的。

以下是带有一些示例内容的响应格式化布局;

{
 - response: {
   |- ips: [
      |- {
         ip: "xxx.xxx.xxx.xxx",
         classification: "threat",
         hits: xx,
         latitude: null,
         longitude: null,
         zone_name: "domain-example1"
         },
       - {
         ip: "yyy.yyy.yyy.yyy",
         classification: "robot",
         hits: yy,
         latitude: null,
         longitude: null,
         zone_name: "domain-example2"
         }
       ]
   }
  result : "success",
  msg: null
}

我尝试了几个不同的组件-SuperObjectPaweł Głowacki的JSON Designtime解析器Tiny-JSONLKJSON和内置的DBXJSON。但是,我对JSON没有任何经验,似乎找不到最基本的示例可以入门。其中许多示例显示样本数据,但我尝试的所有示例似乎都不能按照我的期望工作,很可能是因为我误解它们了。我想这些组件应该是可行的,所以我需要指导如何开始。
在“ips”“数组”中有数百个甚至上千个结果(如果那不正确,我道歉,我认为它被称为数组,但是我完全不了解JSON)。
我真正需要的是一些极其基础的样例代码,然后我可以从中构建(以及用于解析的哪个组件等)。
例如,如果我想从JSON结果获取每个“ip”,并将每个“ip”作为单独的项目放入TListBox(使用TListBox.add方法),我该怎么做?
当我说“ip”时,我指的是该值(在上面的格式化布局中,这将是“xxx.xxx.xxx.xxx”或“yyy.yyy.yyy.yyy”)。
此外,如果我想根据IP从JSON结果中找到一个“记录”,并将数据输出到Delphi数组中,例如:
Result : Array of String = ['"xxx.xxx.xxx.xxx"','"threat"','xx','null','null','"domain-example1"'];

这是否可以使用JSON实现?(如果您认为这是一个单独的问题或与主题无关,则可以编辑它,而不是关闭整个问题)。我最接近的情况是,在每个单独的TListItem中不仅有IP地址,而且还有其他所有数据(即响应,ips,ip,分类,xxx.xxx.xxx.xxx以及每个非空项之间还有多个空项)。我确定这非常简单,但是JSON上有太多信息,对于刚接触该格式的人来说有点让人不知所措。最好的问候,Scott Pritchard.
2个回答

8

JSON非常简单易懂,一旦你理解了基本概念。请参考http://json.org,它会对这些概念进行解释。

JSON有四个基本概念:

是任何JSON元素:基本字符串或数字、数组或对象(除了键值对)。

数组应该是一个熟悉的概念:有序值列表。与Delphi数组的主要区别在于,JSON数组没有为元素定义类型;它们只是“JSON值的数组”。

键值对是一个键值对。键可以是字符串或数字,值可以是任何JSON值。

对象是JSON键值对的关联映射。你可以将其概念上视为TDictionary<string, JSON value>

因此,如果我想将一个JSON数据数组放入TListBox中,我会像这样做(DBXJSON示例,警告:未经测试):

procedure TMyForm.LoadListBox(response: TJSONObject);
var
  i: integer;
  ips: TJSONArray;
  ip: TJSONObject;
  pair: TJSONPair;
begin
  ListBox.Clear;
  pair := response.Get('ips');
  if pair = nil then
    Exit;
  ips := pair.value as TJSONArray;
  for i := 0 to ips.size - 1 do
  begin
    ip := ips.Get(i) as TJSONObject;
    pair := ip.Get('ip');
    if pair = nil then
      ListBox.AddItem('???', ip.Clone)
    else ListBox.AddItem(pair.JsonString, ip.Clone);
  end;
end;

然后你将拥有一个IP地址列表,以及相关对象,如果用户选择其中一项,则可以获取完整记录。如果您想将每个记录的整个内容放入列表控件中,请查看 TListView。它比 TListBox 更适用于这种情况。

如果您想构建一个包含所有值的字符串数组,请使用以下方法:

function JsonObjToStringArray(obj: TJsonObject): TArray<string>;
var
  i: integer;
begin
  SetLength(result, obj.Size);
  for i := 0 to obj.Size - 1 do
    result[i] := obj.Get(i).JsonValue.ToString;
end;

当然,这只是示例代码,但应该可以为您提供构建的基础。


非常感谢Mason!答案很好,我现在理解了这些概念。不过我有点困惑,也许我在原始问题中没有解释清楚(我在开头说了,但是到问题结束时很容易忘记)。我应该提到,我已经在TMemo中获得了纯文本JSON(即通过API检索的未解析字符串),因此无法弄清楚如何将其lines.text放入所需的JSONObject响应中。此外,ips:= pair.value as TJSONArray返回E2015 - Operator not applicable - Scott P
1
@scott:看一下TJSONObject.Parse。那行代码应该使用pair.JsonValue而不是之前的写法。我的错。 - Mason Wheeler
为了以后的参考(和任何其他需要它的人),您需要在使用子句中包含System.JSON单元,以便示例能够正常工作。 - Jacques Koekemoer

1

编辑2: AV问题已经轻松解决。

编辑: 经过进一步检查我的代码,我意识到它会导致大量的内存泄漏。然而,我已经转向使用SuperObject,发现只需2行代码、2个变量即可实现相同的结果,且没有内存泄漏。

Procedure ParseIPs;
  ISO : ISuperObject;
  MyItem : ISuperObject;
begin
  ISO := SO(RetrievedJSON);
  for MyItem in ISO['response.ips'] do Memo2.Lines.Add(MyItem.S['ip']);
end;

RetrievedJSON 是一个简单的 string,其中包含未解析的纯文本 JSON(即不是 JSONString 而是实际的字符串)。

出于连续性考虑,我已经保留了原始代码。


在Mason Wheeler之前的回答以及“teran”在问题9608794上提供的答案的帮助下,我成功构建了以下内容,以将其解析到实际级别(即包含所需访问数据的“数组”),然后将所有具有特定JSONString.Value的项目输出到列表框(在下面的示例中命名为LB1);

Procedure ParseIP;
var
  o, Jso, OriginalObject : TJSONObject;
  ThePair, JsPair : TJSONPair;
  TheVal, jsv : TJSONValue;
  jsArr : TJsonArray;
  StrL1 : String;
  i, num : Integer;
begin
  num := 0;
  o := TJSONObject.ParseJSONValue(TEncoding.ASCII.GetBytes(Memo1.Text), 0) as TJSONObject;
  ThePair := o.Get('response');
  TheVal := ThePair.JsonValue;
  STRL1 := TheVal.ToString;
  JSV := TJSONObject.ParseJSONValue(STRL1);
  OriginalObject := JSV as TJSONObject;
  JSPair := OriginalObject.Get('ips');
  JSARR := JSPair.JsonValue as TJSONArray;
  for i := 0 to JsArr.Size-1 do
    begin
      JSO := JSArr.Get(i) as TJSONObject;
      for JSPAIR in JSO do
        begin
        num := num+1;
          if JSPAIR.JsonString.Value = 'ip' then
          begin
            LB1.Items.Add(JSPair.JsonValue.Value);
          end
          else null;
        end;
    end;
    ShowMessage('Items in listbox: ' + IntToStr(LB1.Items.Count));
    ShowMessage('Items in JSON: ' + IntToStr(num div JSO.Size));
    Jsv.Free;
end;

虽然这是一种非常迂回的方法,但它允许我查看每个单独的步骤,并查看它在JSON中的迭代方式以及极其容易地将其更改为函数,其中我可以基于多个条件输出任何数据片段或范围作为结果。为了验证我得到了正确的项目数量,我在末尾添加了两个ShowMessage例程;一个用于列表框中的项目,另一个用于我正在解析的“ip”数据实例的数量。

此代码专门针对使用CloudFlare API JSON结果的Firemonkey进行测试,这些结果与检索到的内容完全相同(在&calls_left&a=zone_ips&class=t&geo=1 API调用中,当然还附加了您的zonetokenemail)。它应该相对容易地修改为适用于其他来自众多其他API调用的结果。

为了澄清,我确实尝试了梅森的代码,但不幸的是它没有能够正常工作。不过,基于他提供的基础原理解释很有价值,并帮助我找到了一个最终解决方案以及能够自学的东西,因此我暂时接受了他的答案。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接