如何使用 jq 对任意 JSON 进行完全排序?

How can I completely sort arbitrary JSON using jq?

我想比较两个 JSON 文本文件。不幸的是,它们是以任意顺序构造的,所以当它们在语义上相同时,我会得到差异。我想使用 jq (或其他)以任何类型的完整顺序对它们进行排序,以消除仅由于元素排序而导致的差异。

--sort-keys 解决了一半的问题,但它没有对数组进行排序。

我对 jq 非常无知,不知道如何编写保留所有数据的 jq 递归过滤器;任何帮助将不胜感激。

我意识到逐行 'diff' 输出不一定是比较两个复杂对象的最佳方式,但在这种情况下我知道这两个文件非常相似(几乎相同)并且行逐行差异对我来说很好。

回答了一个非常相似的问题,但没有打印差异。另外,我想保存排序的结果,所以我真正想要的只是一个过滤程序来排序 JSON.

这是一个使用通用函数 sorted_walk/1 的解决方案(如此命名的原因在下面的后记中描述)。

normalize.jq:

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

def normalize: sorted_walk(if type == "array" then sort else . end);

normalize

使用 bash 的示例:

diff <(jq -S -f normalize.jq FILE1) <(jq -S -f normalize.jq FILE2)

POSTSCRIPT:walk/1 的内置定义在首次发布此回复后进行了修改:它现在使用 keys_unsorted 而不是 keys

I want to diff two JSON text files.

使用 jd-set 选项:

无输出表示无差别

$ jd -set A.json B.json

差异显示为 @ 路径和 + 或 -。

$ jd -set A.json C.json

@ ["People",{}]
+ "Carla"

输出差异也可以作为带有 -p 选项的补丁文件。

$ jd -set -o patch A.json C.json; jd -set -p patch B.json

{"City":"Boston","People":["John","Carla","Bryan"],"State":"MA"}

https://github.com/josephburnett/jd#command-line-usage

我很惊讶这不是一个更受欢迎的 question/answer。我还没有看到任何其他 json 深度排序解决方案。也许每个人都喜欢一遍又一遍地解决同一个问题。

这是 的包装器,将其包装到 shell 脚本中,该脚本可在管道中运行或使用文件参数。

#!/usr/bin/env bash

# json normalizer function
# Recursively sort an entire json file, keys and arrays
# jq  --sort-keys is top level only
# Alphabetize a json file's dict's such that they are always in the same order
# Makes json diff'able and should be run on any json data that's in source control to prevent excessive diffs from dict reordering.

[ "${DEBUG}" ] && set -x
TMP_FILE="$(mktemp)"
trap 'rm -f -- "${TMP_FILE}"' EXIT

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

def normalize: sorted_walk(if type == "array" then sort else . end);

normalize
EOT

# Don't pollute stdout with debug output
[ "${DEBUG}" ] && cat $TMP_FILE > /dev/stderr

if [ "" ] ; then
    jq -S -f ${TMP_FILE}  
else
    jq -S -f ${TMP_FILE} < /dev/stdin
fi