Delphi如何解析JSON数组或者序列?

9

这是我想要解析的示例JSON:

[
  {
    "a":{
      "username":"aaa",
      "email":"aaa@gmail.com"
    }
  },
  {
    "b":{
      "username":"bbb",
      "email":"bbb@gmail.com"
    }
  }
]

我需要调用getData('b', 'email')函数时输出bbb@gmail.com!
我真的很难理解如何使用System.JSON单元,但我找不到解决方案! 我想编写一个函数,从上述JSON结构中提取特定的数据。 这是我目前的代码。 在类构造函数中,我有:
var
  FIds: TJSONArray;
begin
  FIds := TJSONObject.ParseJSONValue({json string here}) as TJSONArray;
end;

然后,在必须返回数据的函数内部,我写下了这段代码:

// 'name' can be 'a' or 'b'  | 'data' can be 'username' or 'email'
function TTest.getData(const name, data: string): string;
var
  FValue, FValueInner: TJSONValue;
begin
  for FValue in Fids do
  begin
    if (FValue is TJSONArray) then
    begin
      //Here I am sure that I have a TJSONArray (which can be 'a' or 'b' from above)
    end;
  end;
end;

根据我上面所写的,我需要检查name的值并决定是否访问ab内部的数据。然后,一旦我选择了正确的JSON数组ab,我需要选择是否显示usernameemail字段(这是在data变量中指定的)。
我该怎么做呢?
这是我最新的尝试,但我真的不知道该怎么做:
... same code above ...

if (FValue is TJSONArray) then
begin
  //here I want to check if the current array is a or b
  if ((FValue as TJSONArray).Items[0] as TJSONValue).Value = name then
  begin
    Farr := TJSONObject.ParseJSONValue(((FValue as TJSONArray).Items[0] as TJSONValue).ToJSON) as TJSONArray;
    try
      //here I want to get the data inside username or email
      for FValueInner in Farr do
        Result := FValueInner.GetValue<string>(data);
    finally
      Farr.Free;
    end;
  end;
end;

Farr: TJSONArray;FValueInner: TJSONValue; 是什么?


4
虽然我无法直接在这里提供代码,但我的建议是使用XSuperObject而不是内置库。它易于使用且非常轻量级。 - Jerry Dodge
@JerryDodge,也许你的方法就是答案。实际上,我在文档中读到了有三种读取JSON的方式,但我并不喜欢它们,也觉得很难理解。我不想放弃,但如果XSuperObject更容易使用的话,我一定会去找它的! - Raffaele Rossi
3
在我看来,问题似乎出在您对JSON的理解上,而不是System.JSON单元上。从您所写的内容中可以看出,您将'a'和'b'视为数组,但它们实际上是对象,而不是数组! 数组以[ ]开头,但对象以{ }开头。如果您理解了这一点,就会发现该库并不难使用 ;) - Alberto Miola
没有任何反对这个问题的意思,但是我现在又有些困惑了。为什么“Delphi解析JSON数组或数组”比一个具体的主题提问更容易搜索呢?我认为没有理由关闭甚至删除这个问题,因为它是可以理解和回答的问题(就像一些从这里关闭和删除的问题一样)。 - Victoria
花一些时间在 http://json.org 上学习术语。 - David Heffernan
3个回答

14

对于寻找这些答案的新读者。

关于这个函数,或者如果您重构JSON数据,甚至可以更简单?

function getData(JsonString: String; User: String; Field: String): String;
var
  JSonValue: TJSonValue;
  JsonArray: TJSONArray;
  ArrayElement: TJSonValue;
  FoundValue: TJSonValue;
begin
  Result :='';

  // create TJSonObject from string
  JsonValue := TJSonObject.ParseJSONValue(JsonString);

  // get the array
  JsonArray := JsonValue as TJSONArray;

  // iterate the array
  for ArrayElement in JsonArray do begin
      FoundValue := ArrayElement.FindValue(User);
      if FoundValue <> nil then begin
        Result := ArrayElement.GetValue<string>(User + '.' + Field);
        break;
      end;
  end;
end;

上述示例JSON代码的问题在于使用用户名称"a" "b"作为JSON-key{key:data}来表示用户数据。这样,您无法使用GetValue("a")搜索数据。通过以不同方式构造JSON数据可以使搜索过程更加容易。稍后我将给出一个例子。

处理给定的JSON数据的方法是使用FindValue,这样就可以检查是否存在具有key"a"或"b"的字段。

FoundValue := ArrayElement.FindValue("b");
if FoundValue <> nil then begin
    Result := ArrayElement.GetValue<string>("b"+ '.' + "email");
    break;
关于“解析JSON数组”问题:在将数据加载为TJSonObject后,您可以将数据转换为TJSONArray并迭代元素。
  JsonValue := TJSonObject.ParseJSONValue(JsonString);  
  JsonArray := JsonValue as TJSONArray;
  for ArrayElement in JsonArray do begin
    ...

给定JSON数据的工作示例代码:

unit JsonArray1;

interface

uses System.JSON;

function getData2(JsonString: String; User: String; Field: String): String;
procedure Test1();

implementation

function getData2(JsonString: String; User: String; Field: String): String;
var
  JSonValue: TJSonValue;
  JsonArray: TJSONArray;
  ArrayElement: TJSonValue;
  FoundValue: TJSonValue;
begin
  Result :='';

  // create TJSonObject from string
  JsonValue := TJSonObject.ParseJSONValue(JsonString);

  // get the array
  JsonArray := JsonValue as TJSONArray;

  // iterate the array
  for ArrayElement in JsonArray do begin
      FoundValue := ArrayElement.FindValue(User);
      if FoundValue <> nil then begin
        Result := ArrayElement.GetValue<string>(User + '.' + Field);
        break;
      end;
  end;
end;

procedure Test1();
var
  DataBase: String;
  EmailAddress : String;
  Username: String;
begin
  DataBase := '[  {"a" : {"username":"aaa","email":"aaa@gmail.com"}},' +
                 '{"b" : {"username":"bbb","email":"bbb@gmail.com"}}  ]';

  EmailAddress := getData2(DataBase, 'b', 'email');
  Username := getData2(DataBase, 'a', 'username');

end;

end.

正如已经提到的那样,通过使用适当的键重构JSON数据可以使查找数据的代码更加简单。因为用户数据之间存在1对1的关系,比如:"a":{},"b":{},所以很容易引入一个名为'用户'的键。同时向数组中添加一个名为'用户'的键会导致所有数据都有键。

  '{"users" : [{ "user":"a", "username":"aaa","email":"aaa@gmail.com"},' +
              '{ "user":"b", "username":"bbb","email":"bbb@gmail.com"}]}';
当您遍历用户时,现在可以使用新的 "user" 键来使用 GetValue。
  if ArrayElement.GetValue<String>('user') = 'b' then begin
    Result := ArrayElement.GetValue<String>('email');

通过为数组指定键,您现在可以使用以下代码获取该数组:

JsonArray := JsonValue.GetValue<TJSONArray>('users');

重组后的JSON数据的工作示例代码:

unit JsonArray2;

interface

uses System.JSON;

function getData2(JsonString: String; User: String; Field: String): String;
procedure Test2();

implementation

function getData2(JsonString: String; User: String; Field: String): String;
var
  JSonValue: TJSonValue;
  JsonArray: TJSONArray;
  ArrayElement: TJSonValue;
  FoundValue: TJSonValue;
begin
  Result :='';

  // create TJSonObject from string
  JsonValue := TJSonObject.ParseJSONValue(JsonString);

  // get the array
  JsonArray := JsonValue.GetValue<TJSONArray>('users');

  // iterate the array
  for ArrayElement in JsonArray do begin
      if ArrayElement.GetValue<String>('user') = User then begin
        Result := ArrayElement.GetValue<String>(Field);
        break;
      end;
  end;
end;

procedure Test2();
var
  DataBase: String;
  EmailAddress : String;
  Username: String;
begin
  DataBase := '{"users" : [{ "user":"a", "username":"aaa","email":"aaa@gmail.com"},' +
                          '{ "user":"b", "username":"bbb","email":"bbb@gmail.com"}]}';

  EmailAddress := getData2(DataBase, 'b', 'email');
  Username := getData2(DataBase, 'a', 'username');

end;

end.

请注意,Delphi 10.2中的TJSONValue.FindValue是受保护的函数。 - SiBrit

7
你的JSON是一个对象数组,所以FIds是一个包含TJSONObject元素的TJSONArray。而这些对象的ab字段本身就是对象,不是数组。因此,在枚举该数组时,FValue is TJSONArray将始终为false。
另外,(FValue as TJSONArray).Items[0] as TJSONValue).Value = name是错误的,因为JSON对象包含名称/值对,但你忽略了名称,并且尝试将这些对作为数组元素进行枚举,而它们实际上不是。如果你想枚举一个对象的名称/值对,请使用TJSONObject.CountTJSONObject.Pairs[]属性。但在这种情况下,这并不必要,因为你正在寻找特定的一对。 TJSONObject有一个Values[]属性,可以达到这个目的。最后,TJSONObject.ParseJSONValue(((FValue as TJSONArray).Items[0] as TJSONValue).ToJSON) as TJSONArray是完全荒谬的。没有理由将一个对象转换为JSON字符串,只为了再次解析它。它已经被解析过一次了,你不需要再次解析。
最后,FValueInner.GetValue<string>(data)是错误的,因为TJSONValue没有GetValue()方法,更别说使用泛型了。
现在,尝试像这样做:
// 'name' can be 'a' or 'b'  | 'data' can be 'username' or 'email'
function TTest.getData(const name, data: string): string;
var
  FValue, FValueInner: TJSONValue;
begin
  Result := '';
  for FValue in Fids do
  begin
    if (FValue is TJSONObject) then
    begin
      FValueInner := TJSONObject(FValue).Values[name];
      if FValueInner <> nil then
      begin
        if (FValueInner is TJSONObject) then
        begin
          FValueInner := TJSONObject(FValueInner).Values[data]; 
          if FValueInner <> nil then
            Result := FValueInner.Value;
        end;
        Exit;
      end;
    end;
  end;
end;

非常感谢。我不理解的是何时必须使用TJSONObject和TJSONValue。我已经阅读了文档多次,但仍然有一些疑问。TJSONValue是数组内部的项,而TJSONObject是通用对象吗?(数组或项) - Raffaele Rossi
1
TJSONValue 是一个基类,所有其他 JSON 值类都从它派生而来,比如 TJSONObject。你可以有一个字符串数组、一个整数数组、一个对象数组等等。JSON 对象包含名称/值对,可以是数组、整数、字符串、对象等等。因此,API 中的所有内容都作为通用的 TJSONValue 指针公开,你可以将其强制转换为需要访问的特定数据类型... - Remy Lebeau
1
在这种情况下,您有一个指向TJSONObject实例的TJSONValue数组。其中具有包含指向TJSONObject实例的TJSONValueab字段的TJSONPair。该实例具有包含指向TJSONStringTJSONValueusernameemail字段的TJSONPair - Remy Lebeau

2

如果您使用 Alcinoe 的 TalJsonDocument,则以下内容将变得简单:

aJsonDoc := TalJsonDocU.create;
aJsonDoc.loadFromFile(...);
for i := 0 to aJsonDoc.node.childnodes.count-1 do begin
  myValue := aJsonDoc.node.childNodes[i].getchildNodeValueText(['b', 'email']); 
  if myValue <> '' then break;
end;

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