删除所有空值。

24

我正在尝试使用jq从JSON对象中删除空值。 我在他们的GitHub上发现了此问题,因此现在我正在尝试使用del将它们删除。 我有这个:

'{ id: $customerId, name, phones: ([{ original: .phone }, 
 { original: .otherPhone}]), email} | del(. | nulls)'

这似乎没有任何作用。然而,如果我将nulls替换为.phones,它会删除电话号码。


1
请扩展此内容以使其足够完整,可以运行,并显示输入、实际输出和期望输出。 (例如,您可以在heredoc中提供最小的JSON输入,通过运行jq'...your code here...'<<'EOF',包括一些演示问题的JSON,并在之后加上EOF)。另请参阅帮助中心有关最小完整可验证示例的文档 - Charles Duffy
就目前而言,很难确定在您的数据结构中实际上存在哪些 NUL 字符,而这对于构建或测试解决方案非常重要。 - Charles Duffy
请注意,虽然我正在投票关闭当前的问题,但是(1)除非其他人同意,否则它实际上不会被关闭,而且(2)编辑一个已关闭的问题会自动将其放入队列中以供重新打开考虑,因此扩展它以包括一个MCVE应该会导致该关闭被撤销。 - Charles Duffy
1
@CharlesDuffy - 关于您对“NULs”的评论 - (1) OP正在询问JSON的“null”,而不是“NUL”; (2) 对于任何了解jq的人来说,null的位置是清楚的; (3) 对于任何了解JSON的人来说,这是相当明显的。 - peak
@peak,如果没有跟随github链接查看手头的数据,对我来说不是很清楚,而且只有在给定外部内容时才有意义的问题在这里明确不完整,这在元上是非常确定的,因此我请求在问题本身中包括实际输入。是的,一个人也可以将输入数据编辑到问题本身中——我们两个人中的任何一个都那样做,就没有了争议的理由。话虽如此,要求OP自己这样做有助于减少他们未来犯同样错误的可能性。 - Charles Duffy
8个回答

32

这个答案是由Michael Homerhttps://unix.stackexchange.com上提供的,它提供了一个超级简洁的解决方案,适用于jq 1.6及以上版本:

del(..|nulls)

它会从你的JSON中删除所有空值属性(和值)。简单而甜美 :)

nulls是一个内置过滤器,可以被自定义选择替换:

del(..|select(. == "value to delete"))

为了根据多个条件删除元素,例如删除所有布尔值和所有数字:

del(..|booleans,numbers)

或者,仅删除不符合条件的节点:
del(..|select(. == "value to keep" | not))

(最后一个示例只是说明性的 - 当然您可以交换 == 和 != ,但有时这是不可能的。例如,要保留所有真值:del(.. | select(. | not))

2
链接答案下的评论建议使用 jq 'del(recurse(.[]?;true)|select(. == null))' 适用于版本1.5,我已经成功使用了。 - Skippy le Grand Gourou

22
以下说明如何从JSON对象中删除所有空值键:
jq -n '{"a":1, "b": null, "c": null} | with_entries( select( .value != null ) )'
{
  "a": 1
}

或者,可以按照以下方式使用paths/0:

. as $o | [paths[] | {(.) : ($o[.])} ] | add

顺便提一下,del/1也可以用来实现同样的结果,例如使用这个过滤器:

reduce keys[] as $k (.; if .[$k] == null then del(.[$k]) else . end)

或者更为简洁,但不那么明显的是:

del( .[ (keys - [paths[]])[] ] )

同时,以下是使用delpaths/1的两种方式:

jq -n '{"a":1, "b": null, "c": null, "d":2} as $o
  | $o
  | delpaths( [  keys[] | select( $o[.] == null ) ] | map( [.]) )'


$ jq -n '{"a":1, "b": null, "c": null, "d":2}
  | [delpaths((keys - paths) | map([.])) ] | add'

在以上两种情况下,输出结果相同: { "a": 1, "d": 2 }


参考资料:如果您想从JSON文本中的所有JSON对象中删除空值键(即递归地),您可以使用walk/1或:

del(.. | objects | (to_entries[] | select(.value==null) | .key) as $k | .[$k])

在像 del/1 这样的函数引用中,/1 是什么意思?我在其他答案中也看到过它,但在 jq 的文档中没有找到相关说明。 - Mr. Lance E Sloan
3
这是函数的arity。最近版本的jq有一个名为builtins的内置函数,它会发出一个字符串数组,形式为FUNCTOR/ARITY。 - peak

8
到目前为止,这里的所有其他答案都是针对旧版本的jq的解决方法,而且不清楚如何在最新发布的版本中简单地执行此操作。在JQ 1.6或更新版本中,以下内容将递归地删除null:
$ jq 'walk( if type == "object" then with_entries(select(.value != null)) else . end)' input.json

参考来自于在上游讨论增加walk()函数问题时的此评论


4
[警告:本响应中给出的walk/1定义存在问题,特别是第一条评论中提到的原因;还要注意,jq 1.6以不同方式定义walk/1]。 我添加新答案以强调@ jeff-mercado扩展版本脚本。 我的脚本版本假定空值如下:

  • null;
  • [] - 空数组;
  • {} - 空对象。

删除空数组和对象的代码来自于此处https://dev59.com/O18e5IYBdhLWcg3wAWgL#26196653

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

walk(
    if type == "object" then
        with_entries(select( .value != null and .value != {} and .value != [] ))
    elif type == "array" then
        map(select( . != null and . != {} and .!= [] ))
    else
        .
    end
)

1
def walk(f) 中添加 select(length > 0) | 是一个错误。对于某些 JSON,它会删除非空数组。对于其他 JSON(例如,嵌套对象,每个对象包含一些 null 和空结构),只返回 {}。然而,这里显示的条件与 walk 的原始定义相匹配。我刚刚注意到这个问题,所以我无法更改昨天的投票。 - Mr. Lance E Sloan
更优雅的解决方案请参考以下链接:https://github.com/stedolan/jq/issues/104#issuecomment-707806841 - jsxt
请点击以下链接查看更优雅的解决方案:https://github.com/stedolan/jq/issues/104#issuecomment-707806841 - undefined

2

这并不是 del/1 函数的正确使用方式。如果你想要移除一个对象的 .phones 属性,你应该这样做:

del(.phones)

换句话说,del 的参数是您想要删除的属性路径。
如果您想使用此方法,您需要找到所有指向 null 值的路径并将其传递给该方法。但这可能比较麻烦。
流式输入可以使此任务更加简单。
fromstream(tostream | select(length == 1 or .[1] != null))

否则,为了更简单的方法,你需要遍历对象树以查找null值。如果发现,就将其过滤掉。使用walk/1,你可以递归地应用过滤器来排除null值。
walk(
    (objects | with_entries(select(.value != null)))
    // (arrays | map(select(. != null)))
    // values
)

因此,如果您有以下输入:

{
    "foo": null,
    "bar": "bar",
    "biz": [1,2,3,4,null],
    "baz": {
        "a": 1,
        "b": null,
        "c": ["a","b","c","null",32,null]
    }
}

这个过滤器将产生以下结果:
{
    "bar": "bar",
    "baz": {
        "a": 1,
        "c": ["a","b","c","null",32]
    },
    "biz": [1,2,3,4]
}

Jeff - 首先,使用walk,您可以只写:walk(如果type ==“object”,则with_entries(select(.value!= null))elif type ==“array”,则map(select(. != null))else . end)。其次,walk / 1已经在开发版本中可用。 - peak
啊,你说得对,我没有意识到它是深度优先搜索。谢谢。 - Jeff Mercado

1
在本页面的其他地方,已经表达了一些使用jq递归消除[]、{}以及null的兴趣。
虽然可以使用内置的walk/1定义来实现这一点,但正确地这样做有点棘手。因此,这里是一个变体版本的walk/1,使得这个过程变得非常简单。
def traverse(f):
  if type == "object" then map_values(traverse(f)) | f
  elif type == "array" then map( traverse(f) ) | f
  else f
  end;

为了方便修改删除元素的标准,我们定义:
def isempty: .==null or ((type|(.=="array" or .=="object")) and length==0);

现在的解决方案很简单:
traverse(select(isempty|not))

1

使用更新版本的 jq(1.6 及更高版本)

您可以使用此表达式递归地删除空值键:

jq 'walk( if type == "object" then with_entries(select(.value != null)) else . end)'

REF

{{链接1:REF}}


1
这个答案与一年前Caleb的答案完全相同? - knittl

0

[警告:下面的响应存在几个问题,其中最主要的是由于0|length为0而引起的。]

早期答案的基础上,除了删除具有null值的属性外,还经常有助于删除具有空数组或对象值(即[]{})的JSON属性。

ℹ️ — jq的walk()函数(walk/1)使此操作变得容易。walk()将在未来版本的jq(>jq-1.5)中提供,但其定义可以添加到当前过滤器中。

用于删除null和空结构的条件是:

walk(
  if type == "object" then with_entries(select(.value|length > 0))
  elif type == "array" then map(select(length > 0))
  else .
  end
)

给定以下 JSON 输入:

{
  "notNullA": "notNullA",
  "nullA": null,
  "objectA": {
    "notNullB": "notNullB",
    "nullB": null,
    "objectB": {
      "notNullC": "notNullC",
      "nullC": null
    },
    "emptyObjectB": {},
    "arrayB": [
      "b"
    ],
    "emptyBrrayB": []
  },
  "emptyObjectA": {},
  "arrayA": [
    "a"
  ],
  "emptyArrayA": []
}

使用此函数将得到以下结果:
{
  "notNullA": "notNullA",
  "objectA": {
    "notNullB": "notNullB",
    "objectB": {
      "notNullC": "notNullC"
    },
    "arrayB": [
      "b"
    ]
  },
  "arrayA": [
    "a"
  ]
}

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