如何使用jq从JSON中获取子对象,在没有Bash处理的情况下保留结果中的最终键?

时间:2018-01-19 00:29:52

标签: json bash jq

我正在编写一个Bash函数来获取JSON对象的一部分。该函数的API是:

GetSubobject()
{
   local Filter="$1"    # Filter is of the form .<key>.<key> ... .<key>
   local File="$2"      # File is the JSON to get the subobject

   # Code to get subobject using jq
   # ...
}

为了说明子对象的含义,请考虑Bash函数调用:

GetSubobject .b.x.y example.json

文件example.json包含:

{
   "a": { "p": 1, "q": 2 },
   "b":
   {
      "x":
      {
          "y": { "j": true, "k": [1,2,3] },
          "z": [4,5,6]
      }
   }
}

函数调用的结果将发送到stdout:

{
  "y": {
    "j": true,
    "k": [
      1,
      2,
      3
    ]
  }
}

请注意,代码jq -r "$Filter" "$File"无法提供所需的答案。它会给:

{ "j": true, "k": [1,2,3] }

请注意,我正在寻找的答案需要是我可以在上面的Bash函数API中使用的。因此,答案应该使用上面显示的过滤器和文件变量,而不是特定于上面的示例。

我想出了一个解决方案;但是,它依靠Bash来完成部分工作。我希望解决方案可以是纯jq,而不依赖于Bash处理。

#!/bin/bash

GetSubobject()
{
   local Filter="$1"
   local File="$2"

   # General case: separate:
   #    .<key1>.<key2> ... .<keyN-1>.<keyN>
   # into:
   #   Prefix=.<key1>.<key2> ... .<keyN-1>
   #   Suffix=<keyN>
   local Suffix="${Filter##*.}"
   local Prefix="${Filter%.$Suffix}"

   # Edge case: where Filter = .<key>
   # Set:
   #    Prefix=.
   #    Suffix=<key>
   if [[ -z $Prefix ]]; then
      Prefix='.'
      Suffix="${Filter#.}"
   fi

   jq -r "$Prefix|to_entries|map(select(.key==\"$Suffix\"))|from_entries" "$File"
}

GetSubobject "$@"

如何使用jq完成上述Bash功能以获得所需的结果,希望以不那么暴力的方式利用jq的功能而无需在Bash中进行预处理?

3 个答案:

答案 0 :(得分:1)

如果我理解你正在尝试做的事情,我似乎无法做到这一点&#34;纯粹的jq&#34;已阅读文档(并且自己成为常规jq用户)。我能在这里帮助最接近的是简化jq部分本身:

jq -r "$Prefix| { $Suffix }" "$File"

这与您的示例具有相同的行为(在这组有限的案例中):

GetSubobject '.b.x.y' example.json
{
  "y": {
    "j": true,
    "k": [
      1,
      2,
      3
    ]
  }
}

这实际上是元编程的一种情况,您希望以编程方式对jq程序进行操作。好吧,(对我而言)jq将其程序作为输入但不允许您更改程序本身是有道理的。 bash似乎是在这里进行元编程的合适选择:将jq程序转换为另一个程序,然后使用它运行jq

答案 1 :(得分:1)

进一步简化jq部分,但与JawguyChooser的答案具有相同的一般约束,如何更简单的Bash函数

GetSubject () {
    local newroot=${1##*.}
    jq -r "{$newroot: $1}" "$2"
}

我可能会忽略更复杂的Bash处理的一些细微差别,但这似乎适用于您提供的示例。

答案 2 :(得分:1)

如果目标是在bash中尽可能少地做,那么可能以下bash函数将填写该帐单:

function GetSubobject {
   local Filter="$1"    # Filter is of the form .<key>.<key> ... .<key>
   local File="$2"      # File is the JSON to get the subobject
   jq  '(null|path('"$Filter"')) as $path
        | {($path[-1]): '"$Filter"'}' "$File"
}

另一种方法是将$Filter作为字符串传递(例如--arg filter&#34; $ Filter&#34;),让jq进行解析,然后使用getpath

如果可以使用与感兴趣的字段分开的路径调用GetSubobject,这当然是最简单的,如下所示:

GetSubobject .b.x y filename