使用jq遍历JSON

12

我正在解析一个API,它发送给我一个像这样的JSON响应:

{
  "newList": {
    "243": {
      "id": "243",
      "name": "test",
      "create": {
        "date": "2017-08-31 13:57:29"
      }
    },
    "244": {
      "id": "244",
      "name": "test",
      "create": {
        "date": "2017-08-31 13:57:29"
      }
    }
 }
}

我正在尝试使用bash和jq获取名称和创建日期,但是成功率很低。

jq '.newList'可以奏效并将我带入了一个层级,但这还不够。

jq '.newList .243'给我一个编译错误。此外,243是动态的,随时可能会改变。我在这里做错了什么?

2个回答

22
假设您在问题中给出的根节点名称为newList,要获取id和创建日期,您可以使用jq进行字符串插值。
api-producing-json | jq --raw-output '.newList[] | "\(.id) \(.create.date)"'

这样过滤器就不会受到节点中动态数字(243244)的影响。如果需要带引号的输出,请删除--raw-output标志。

要在循环中进一步处理,可以在bash循环中迭代它。

while read -r id name date; do
    echo "Do whatever with ${id} ${name} ${date}"
done< <(api-producing-json | jq --raw-output '.newList[] | "\(.id) \(.name) \(.create.date)"')

如果你担心某个字段中间有空格,或者想更仔细地分隔单词,可以在过滤器中使用分隔符 |,如"\(.id)|\(.name)|\(.create.date)",并在 while 循环中使用 read 命令,并设置 IFS=|,这样变量就会得到适当的存储。


始终使用官方 jq - 文档 获取帮助,并使用此漂亮的游乐场来测试过滤器:jq - 在线版


如何调整代码以适应从Chrome本机消息传递中的api-producing-jsonstdin的循环?请参阅如何在本机消息传递主机中从stdin解析JSON? - guest271314
如果我想要包含与“id”字段的值不同的“243”和“244”作为字段名称,那么这会是什么样子呢?(因为我的输入没有这种冗余。) - Randall Schulz

0
首先,简单的部分:你需要使用.newList."243"(带双引号)来访问"243"的详细信息。jq将.243解释为数字而不是字符串,而对象键都是字符串。
但正如你所说,事先不知道键名,所以我们不能使用特定的键。没关系,如果需要,有办法获取它们。而且无论如何,你最初的问题只要求名称和创建日期,所以我们可以忽略数字键。
简单版本
为了丢弃键名,我们可以使用values过滤器,然后可以使用map将其他过滤器应用于值。因此,我们的解决方案将类似于 cat input.json | jq ' .newList | values | map(some_filter) '
什么是some_filter?嗯,它可以是任何我们想要的东西,只要它接受正确类型的对象,并输出我们所需的结果。我曾尝试将这些内容写成一行代码,但我发现如果使用def关键字来定义自己的过滤器(就像在其他语言中定义辅助函数一样),那么使用jq会更容易理解。
我的建议解决方案:
cat input.json | jq `
      def details_to_string:
          . | "Details: \(id), \(.name), \(.create.date)";

      .newList | values | map(details_to_string)
`

这是如何工作的?

在我们开始处理输入之前,我们定义了一个名为'details_to_string'的新过滤器。它接受一个具有'id'字段和'.create.date'字段的对象,并使用字符串插值返回一个字符串。(我不确定您想要什么输出格式,但格式化的字符串是一个简单的例子)。请注意'def'行末尾的冒号以及实际过滤器定义末尾的分号。
实际处理从最后一行开始。这是将输入文件中的JSON对象输入程序的地方。
首先,对象经过一个'newList'过滤器,结果是newList对象(其中{ "243": X, "244": Y },其中XY是较小的JSON对象)。
接下来,该对象经过'values'过滤器,结果是值的数组([ X, Y],其中XY是上述相同的较小JSON对象)。
最后,该数组通过'map(details_to_string)'。Map接受一个数组,并通过将其通过'details_to_string'过滤器来更改数组中的每个项。因此,它接受一个类似于[X_details_object, Y_details_object]的数组,并输出一个类似于[X_string, Y_string]的数组。
输出是什么?
[
  "Details: 243, test, 2017-08-31 13:57:29",
  "Details: 244, test, 2017-08-31 13:57:29"
]

如果你只想要单独的字符串(没有方括号,没有逗号),那么你可以在最后将数组拆开。(.newList | values | map(details_to_string) | .[])。如果你还将'jq'改为'jq -r',那么你也会去掉引号。
如果你只想要列表中的第一项,那么你可以用.[0]提取出来:.newList | values | .[0] | details_to_string(注意我们现在不再需要数组,所以不再需要map)。
更有趣的版本
在上面的评论中,你问到如何捕获并包含键"243"和"244",并且你提到这些字符串在原始示例中的字段名为"id"时不可用。对于这个问题,我认为最好使用一个叫做to_entries的过滤器。

to_entries将对象的键和值都转换为值,使其可用。对象内的每个键值对都变成了我所称之为“entry”对象的一部分,它看起来像这样:{"key": some_key, "value": some_value}。请注意,“key”和“value”字面上是字符串“key”和“value”,因此像{"243": "alpha", "244": "Beta"}这样的对象会变成像[{"key": "243", "value": "alpha"}, {"key": "244", "value": "beta"}]这样的数组。

这样可以使键可用,但在数据通过系统时,也可能难以跟踪数据的结构。这就是我发现将事物拆分为函数真正有帮助的地方。

cat input.json | jq '
        def entry_to_list:
            [ .key, .value.name, .value.create.date ];

        def list_to_string:
          "Details:" + .[0] + ", " +  .[1] + ", " + .[2] ;

       .newList | to_entries | map( entry_to_list | list_to_string )
    '

输出:

[
  "Details: 243, test, 2017-08-31 13:57:29",
  "Details: 244, test, 2017-08-31 13:57:29"
]

使用这个版本,您可以获得一个中间步骤,其中包含像`["244", "test", "2017-08-31 13:57:29"]`这样的值,这为对结果进行排序提供了几个选项。(数组首先按照第一个字段排序,然后按照第二个字段排序,以此类推)
  • newList | to_entries | map(entry_to_list) | sort | map(list_to_string)(在转换为字符串之前对条目进行排序)
  • newList | to_entries | map(entry_to_list) | min | list_to_string(在转换为字符串之前选择最低的条目)

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