【问题标题】:jq: easiest way to recursively remove objects based on object value conditionjq:基于对象值条件递归删除对象的最简单方法
【发布时间】:2017-09-06 00:31:49
【问题描述】:

我想使用 jq 删除 JSON“对象”(我通常使用该术语来指代数组或字典)中的所有字典,

a) 包含一个名为“delete_me”的键,并且 b) 键“delete_me”满足某个预定条件(空、非零、真等)的地方

基本上,我要实现的逻辑是:遍历输入,并且在每个节点处,如果该节点不是数组或对象,则保留它并继续前进,否则,保留它但从中删除任何子节点是条件 a) 或 b) 失败的字典。

有什么建议吗?

示例输入:

{
  "a": { "foo": "bar" },
  "b": {
    "i": {
      "A": {
        "i": [
          {
            "foo": {},
            "bar": {
              "delete_if_this_is_null": false,
              "an_array": [],
              "another_array": [
                {
                    "delete_if_this_is_null": null,
                    "foo": "bar"
                }
              ],
              "etc": ""
            },
            "foo2": "s"
          },
          {
            "foo": {
              "an_array": [
                {
                  "delete_if_this_is_null": "ok",
                  "foo":"bar",
                  "another_object": { "a":1 }
                },
                {
                  "delete_if_this_is_null": null,
                  "foo2":"bar2",
                  "another_object": { "a":1 },
                  "name": null
                }
              ],
              "an_object": {
                "delete_if_this_is_null":null,
                "foo3":"bar3"
              }
            },
            "zero": 0,
            "b": "b"
          }
        ]
      }
    }
  }
}

如果“delete_me”键为delete_if_this_is_null且预定条件为delete_if_this_is_null == null,则应该让步:

{
  "a": { "foo": "bar" },
  "b": {
    "i": {
      "A": {
        "i": [
          {
            "foo": {},
            "bar": {
              "delete_if_this_is_null": false,
              "an_array": [],
              "another_array": [],
              "etc": ""
            },
            "foo2": "s"
          },
          {
            "foo": {
              "an_array": [
                {
                  "delete_if_this_is_null": "ok",
                  "foo":"bar",
                  "another_object": { "a":1 }
                }
              ]
            },
            "zero": 0,
            "b": "b"
          }
        ]
      }
    }
  }
}


更新:这是解决方案:假设输入在文件'input.json'中:
jq 'def walk(f):
  . as $in
  | if type == "object" then
      reduce keys[] as $key
        ( {}; . + { ($key):  ($in[$key] | walk(f)) } ) | f
  elif type == "array" then map( walk(f) ) | f
  else f
  end;

def mapper(f):
  if type == "array" then map(f)
  elif type == "object" then
  . as $in
  | reduce keys[] as $key
      ({};
       [$in[$key] | f ] as $value
       | if $value | length == 0 then .
         else . + {($key): $value[0]} end)
  else .
  end;

walk( mapper(select((type == "object" and .delete_if_this_is_null == null) | not)) )' < input.json

【问题讨论】:

  • 请编辑您的问题以包含输入 JSON 和所需输出的示例。
  • 根据您的定义,数组有一个名为“delete_me”的“键”是什么意思?数组具有整数索引。这没有意义。
  • @Jeff,我说我想删除可以是数组或字典的对象中的“所有字典”。我没有说我会删除一个数组。因此输入对象(或其中的任何节点)可以是包含满足条件 a) 和 b) 的字典的数组,在这种情况下,我想从数组中删除该元素。这怎么没有意义?
  • @Jordan,将在今晚晚些时候发布。我当前的数据结构是专有的,所以我必须重新创建通用数据才能发布。
  • 我想更相关的问题是,您所说的“我通常使用该术语来指代数组或字典”是什么意思,您指的是什么?你提出问题的方式有点令人困惑。

标签: json jq


【解决方案1】:

Jeff 的解决方案可能过于频繁。例如,使用:

def data: [1,2, {"hello": {"delete_me": true, "a":3 }, "there": 4} ]; ];

Jeff 的解决方案产生空(即没有)。

因此,以下内容可能更接近您正在寻找的内容:

walk(if (type == "object" and .delete_me) then del(.) else . end )

对于data,这会产生:

[1,2,{"hello":null,"there":4}]

替代解决方案

如果需要消除上述示例中的"hello":null 的解决方案,则需要jq 的map_values/1 的变体。这是一种方法:

def mapper(f):
  if type == "array" then map(f)
  elif type == "object" then
  . as $in
  | reduce keys[] as $key
      ({};
       [$in[$key] | f ] as $value
       | if $value | length == 0 then . 
         else . + {($key): $value[0]} end)
  else .
  end;

data | walk( mapper(select((type == "object" and .delete_me) | not)) )

结果是:

[1,2,{"there":4}]

【讨论】:

  • 谢谢@peak,替代版本通过稍微调整回答了我的问题。对于给定的输入/输出——我故意选择 .delete_me 为空而不是真值的更困难的条件——稍加修改以添加has("target_key")(可能这会使您的某些解决方案变得不必要,但无论如何,它都有效据我所知,正确)。因此,对于我发布的输入/输出,将 and .delete_me 替换为 and has("delete_if_this_is_null") and .delete_if_this_is_null == null 有效
  • 作为主要解决方案的变体,我使用empty 而不是del(.),它消除了我的空值。
【解决方案2】:

这是一个使用递归函数的解决方案:

def clean(condition):
  if type == "object" then
    if condition
    then empty
    else
    with_entries(
      if (.value|type) == "object" and (.value|condition)
      then empty
      else .value |= clean(condition)
      end
    )
    end
  elif type == "array" then
    map(
      if type == "object" and condition
      then empty
      else clean(condition)
      end
    )
  else .
  end
;

clean(
  has("delete_if_this_is_null") and (.delete_if_this_is_null == null)
)

【讨论】:

  • 谢谢,似乎可以解决问题。与其他解决方案相比,这有什么优点/缺点吗?
  • peak 的解决方案和Jeff 的解决方案都很好,但我想演示一个用显式递归编写的解决方案如何工作而不是使用walk/1,因为这样可以得到精确的细节关于如何/何时使用条件的说明很清楚,以便在必要时可以更轻松地扩展逻辑。我也喜欢这种形式如何展示map/1with_entries/1 的相似性。
【解决方案3】:

我不确定您究竟想在您的问题中完成什么,但我假设您想递归搜索 json 响应并删除满足某些条件的 json 对象。

您可以借助 walk 过滤器轻松完成此操作,该过滤器将在 jq 的未来版本中出现,请参阅 source 中的实现。

# Apply f to composite entities recursively, and to atoms
def walk(f):
  . as $in
  | if type == "object" then
      reduce keys[] as $key
        ( {}; . + { ($key):  ($in[$key] | walk(f)) } ) | f
  elif type == "array" then map( walk(f) ) | f
  else f
  end;

这样,您可以像这样过滤掉它们:

def filter_objects(predicate): # removes objects that satisfies some predicate
    walk(
        if (type == "object") and (predicate) then
            empty
        else
            .
        end
    )
    ;
filter_objects(.delete_me) # remove objects that has a truthy property "delete_me"

【讨论】:

  • 谢谢杰夫,你的建议背后的逻辑对我来说似乎是合理的,但我一定遗漏了一些东西——正如@peak 正确指出的那样,它太过分了
  • 抱歉,我主要是在黑暗中工作。我不知道您的对象会有多复杂,也不知道我是否正确解释了您的问题。但这可能会起作用,我会做一些调整。
猜你喜欢
  • 2016-07-13
  • 1970-01-01
  • 1970-01-01
  • 2021-07-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多