从对象中选择一组键:jq

37

给定一个由数组中的键组成的输入JSON字符串,返回一个只包含原始对象和输入数组中具有相应键的条目的对象。

我有一个解决方案,但我认为它不够优雅({($k):$input[$k]}感觉特别笨重...)而且这是我学习的机会。

jq -n '{"1":"a","2":"b","3":"c"}'   \
    | jq --arg keys '["1","3","4"]' \
    '. as $input 
     | ( $keys | fromjson )
     | map( . as $k
          | $input
          | select(has($k))
          | {($k):$input[$k]}
          )
     | add'

你有什么想法可以清理这个?

我觉得用jq从嵌套的JSON对象中提取所选属性是一个不错的起点,但我无法让它工作。


2
从文档中并不清楚我是否可以通过 select(.key == ("1","3","4")) 在 == 语句的 rhs 上使用 ( )。 - Jon
6个回答

39

内部检查的解决方案:

jq 'with_entries(select([.key] | inside(["key1", "key2"])))'

8
这会返回所有以 key1key2 开头的条目,包括 key1234key22 - Elte Hupkes

14

内部运算符在大多数情况下都有效;然而,我发现内部运算符有副作用,有时会选择不需要的键。假设输入为{ "key1": val1, "key2": val2, "key12": val12 }并使用inside(["key12"])进行选择,它将同时选择"key1""key12"

如果需要精确匹配,请使用in运算符:像这样只会选择.key2.key12

jq 'with_entries(select(.key | in({"key2":1, "key12":1})))'

因为 in 运算符只检查对象中的键(或数组中的索引 exists?),所以它必须以对象语法编写,所需的键作为键,但值并不重要;in 运算符对于此目的并不完美,我希望看到实现了 jq 内置的 Javascript ES6 includes API 的反向版本。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes

jq 'with_entries(select(.key | included(["key2", "key12"])))'

检查数组中是否包含一个项目 .key


1
这不应该是对你其他答案的编辑吗? - Nathan Tuggy
但是我可以在同一个线程上有两个答案吗?因为有两种不同的方法。 - user5672998
1
你可以这样做,但通常你会想在建议方法的同一答案中加上警告,这样就不必在决定合适的方法之前阅读整个问答。而且相似的方法通常可以合并到一个答案中。 - Nathan Tuggy

12

您可以使用此过滤器:

with_entries(
    select(
        .key as $k | any($keys | fromjson[]; . == $k)
    )
)

8

以下是额外的澄清

对于输入对象 {"key1":1, "key2":2, "key3":3},我希望删除所有不在所需键集合 ["key1","key3","key4"] 中的键。

 jq -n --argjson desired_keys '["key1","key3","key4"]'  \
       --argjson input '{"key1":1, "key2":2, "key3":3}' \
    ' $input
    | with_entries(
          select(
              .key == ($desired_keys[])
          )
       )'

with_entries{"key1":1, "key2":2, "key3":3} 转换成以下的键-值对数组,并将 select 语句应用于数组,然后将结果数组转换回对象。

这是 with_entries 语句中的内部对象。

[
  {
    "key": "key1",
    "value": 1
  },
  {
    "key": "key2",
    "value": 2
  },
  {
    "key": "key3",
    "value": 3
  }
]

我们可以从这个数组中选择符合我们标准的键。这就是魔法发生的地方......以下是命令中间正在进行的操作。下面的命令将扩展的值数组转换为对象列表,我们可以从中选择。
jq -cn '{"key":"key1","value":1}, {"key":"key2","value":2}, {"key":"key3","value":3}
      | select(.key == ("key1", "key3", "key4"))'

这将产生以下结果。
{"key":"key1","value":1}
{"key":"key3","value":3}

with entries命令可能有一点棘手,但很容易记住它需要一个过滤器,定义如下:

def with_entries(f): to_entries|map(f)|from_entries;

这与之前的内容相同。
def with_entries(f): [to_entries[] | f] | from_entries;

有时人们会对等号右侧的多个匹配项感到困惑。

考虑以下命令。我们可以看到输出结果是左侧列表和右侧列表的外积。

jq -cn '1,2,3| . == (1,1,3)'
true
true
false
false
false
false
false
false
true

如果该谓词出现在一个select语句中,当该谓词为真时,我们保留输入。请注意,您也可以在此处复制输入。

jq -cn '1,2,3| select(. == (1,1,3))'
1
1
3

7

Jeff的答案存在一些不必要的低效率,以下内容解决了这两个问题,假设使用--argjson keys而不是--arg keys

with_entries( select( .key as $k | $keys | index($k) ) )

更好的是,如果你的jq有IN功能:
with_entries(select(.key | IN($keys[])))

1
这很聪明。避免了遍历输入键和期望键的整个外积。 - Jon

5

如果您确定输入数组中的所有键都存在于原始对象中,可以使用对象构造快捷方式。

$ echo '{"1":"a","2":"b","3":"c"}' | jq '{"1", "3"}'
{
  "1": "a",
  "3": "c"
}

为了让jq将数字解释为键而不是字面量,应该用引号括起来。如果键不像数字,就不需要引号:

$ echo '{"key1":"a","key2":"b","key3":"c"}' | jq '{key1, key3}'
{
  "key1": "a",
  "key3": "c"
}

添加一个不存在的键将产生一个空值,这与操作者想要的不太可能相同:

$ echo '{"1":"a","2":"b","3":"c"}' | jq '{"1", "3", "4"}'
{
  "1": "a",
  "3": "c",
  "4": null
}

但是这些可以被过滤掉:

$ echo '{"1":"a","2":"b","3":"c"}' | jq '{"1", "3", "4"} | with_entries(select(.value != null))'
{
  "1": "a",
  "3": "c"
}

尽管这个答案没有像OP要求的那样接收一个有效的输入json数组,但我发现它对于仅筛选一些已知存在的键非常有用。
例如用例:从JWT中获取audiss。以下是非常简洁的代码:
echo "jwt-as-json" | jq '{aud, iss}'

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