如何在jq中将JSON对象转换为键值对格式?

67

在 jq 中,我如何将 JSON 转换为 key=value 形式的字符串?

来自:

{
    "var": 1,
    "foo": "bar",
    "x": "test"
}

收件人:

var=1
foo=bar
x=test
6个回答

116

你可以尝试:

jq -r 'to_entries|map("\(.key)=\(.value|tostring)")|.[]' test.json

这是一个演示:

$ cat test.json
{
    "var": 1,
    "foo": "bar",
    "x": "test"
}
$ jq -r 'to_entries|map("\(.key)=\(.value|tostring)")|.[]' test.json
foo=bar
var=1
x=test

3
我能否以递归的方式完成这个任务? - gianebao
有一个递归函数...但我猜你需要说要在哪个字段上进行递归。你有一个固定的字段想要递归吗,还是只是“任何值都是对象”? - aioobe
@aioobe 你能把键名变成大写吗?我尝试了\(.key | tr a-z A-Z)但是没有成功。 - Dane Jordan
使用 jq -r 'to_entries|map("\(.key|@uri)=\(.value|@uri)")|join("&")' 可以得到一个有效的查询字符串。 - cachius
为了保护值并将其引用(例如在shell脚本中使用),请使用:jq -r 'to_entries|map("\(.key)=" + @sh "\(.value|tostring)")|.[]'。自jq版本1.3以来,@sh字符串格式化程序可用。 - ack
显示剩余4条评论

5
这个问题有没有递归的解决方法?下面是一个可能符合你要求的函数:
# Denote the input of recursively_reduce(f) by $in.
# Let f be a filter such that for any object o, (o|f) is an array.
# If $in is an object, then return $in|f;
# if $in is a scalar, then return [];
# otherwise, collect the results of applying recursively_reduce(f)
# to each item in $in.
def recursively_reduce(f):
  if type == "object" then f
  elif type == "array" then map( recursively_reduce(f) ) | add
  else []
  end;

例子:发出键=值对。
def kv: to_entries | map("\(.key)=\(.value)");


[ {"a":1}, [[{"b":2, "c": 3}]] ] | recursively_reduce(kv)
#=> ["a=1","b=2","c=3"]

更新:在jq 1.5发布后,walk/1被添加为jq定义的内置函数。它可以与上述定义的kv一起使用,例如:

 walk(if type == "object" then kv else . end) 

使用以上输入,结果将是:

[["a=1"],[[["b=2","c=3"]]]]

为了“展开”输出,可以使用flatten/0。以下是一个完整的示例:

jq -cr 'def kv: to_entries | map("\(.key)=\(.value)");
        walk(if type == "object" then kv else . end) | flatten[]'

输入:

[ {"a":1}, [[{"b":2, "c": 3}]] ]

输出:

a=1
b=2
c=3

5

顺便提一下,基于@aioobe的出色回答。如果您需要将键全部大写,可以使用ascii_upcase通过修改他的示例来实现:

jq -r 'to_entries|map("\(.key|ascii_upcase)=\(.value|tostring)")|.[]'

示例

我有一个与您类似的情况,但是在创建用于访问AWS的环境变量时想要大写所有键。

$ okta-credential_process arn:aws:iam::1234567890123:role/myRole | \
     jq -r 'to_entries|map("\(.key|ascii_upcase)=\(.value|tostring)")|.[]'
EXPIRATION=2019-08-30T16:46:55.307014Z
VERSION=1
SESSIONTOKEN=ABcdEFghIJ....
ACCESSKEYID=ABCDEFGHIJ.....
SECRETACCESSKEY=AbCdEfGhI.....

References


全部大写是一个非常糟糕的主意;那里有像LD_LIBRARY_PATHPATHLD_PRELOAD这样安全敏感的东西。你应该确保不受信任的变量具有小写名称,以防止它们破坏你的操作系统。请参阅https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html(“包含小写字母的环境变量名命名空间专用于应用程序”)。 - Charles Duffy

2

没有使用jq,我可以使用grepsed 导出json中的每个条目,但这仅适用于简单情况,即我们只有键/值对。

for keyval in $(grep -E '": [^\{]' fileName.json | sed -e 's/: /=/' -e "s/\(\,\)$//"); do
    echo "$keyval"
done

这里是一个示例响应:

for keyval in $(grep -E '": [^\{]' config.dev.json | sed -e 's/: /=/' -e "s/\(\,\)$//"); do
    echo "$keyval"       
done
"env"="dev"
"memory"=128
"role"=""
"region"="us-east-1"

1
这是一个简洁的解决方案:to_entries[]|join("=")
$ echo '{"var": 1, "foo": "bar", "x": "test"}' | \
jq -r 'to_entries[]|join("=")'
var=1
foo=bar
x=test

1
aaiobe的回答的基础上,以下是一些使其更安全/更灵活使用的要点:
  • 对于null的安全性使用问号
  • 支持一级嵌套数组
  • 使用jsn_前缀对生成的变量进行命名空间处理,以避免覆盖您的普通变量
$ cat nil.jsn
null
$ jq -r 'to_entries? | map("set -A jsn_\(.key|@sh) -- \(.value|@sh)") | .[]' <nil.jsn
$ cat test.jsn
{
  "var": 1,
  "foo": [
    "bar",
    "baz",
    "space test"
  ],
  "x": "test"
}
$ jq -r 'to_entries? | map("set -A jsn_\(.key|@sh) -- \(.value|@sh)") | .[]' <nil.jsn
$ jq -r 'to_entries? | map("set -A jsn_\(.key|@sh) -- \(.value|@sh)") | .[]' <test.jsn
set -A jsn_'var' -- 1
set -A jsn_'foo' -- 'bar' 'baz' 'space test'
set -A jsn_'x' -- 'test'

以上输出是Korn Shell数组(适用于mksh和AT&T ksh)。如果您偏好/需要GNU bash风格的数组(也被许多其他最新的shell版本支持),请使用:
$ jq -r 'to_entries? | map("jsn_\(.key | @sh)=(\(.value | @sh))") | .[]' <nil.jsn
$ jq -r 'to_entries? | map("jsn_\(.key | @sh)=(\(.value | @sh))") | .[]' <test.jsn
jsn_'var'=(1)
jsn_'foo'=('bar' 'baz' 'space test')
jsn_'x'=('test')

在所有后续的示例中,我将使用 Korn Shell 变体,但对上述两个变体之间的差异应用将适用于它们。

在这两种情况下,您都会得到数组,但对数组本身进行解引用与解引用其元素 #0 是相同的,因此即使对于单个值也是安全的:

$ eval "$(jq -r 'to_entries? | map("set -A jsn_\(.key|@sh) -- \(.value|@sh)") | .[]' <test.jsn)"
$ echo $jsn_x
test
$ echo $jsn_foo = ${jsn_foo[0]} but not ${jsn_foo[1]}
bar = bar but not baz

然而,它也有一个缺点:仅为sh编写的脚本不支持数组,因此您需要选择bashksh88ksh93mkshzsh(可能还有其他选项),或者它们的共同子集(所有这些都支持[[ … ]];除了ksh88之外,其他都应该支持GNU bash风格的数组)。


两个进一步的改进:
  • 手动处理null(见下文)
  • 从白名单中过滤顶级键
$ jq -r 'if . == null then
>         null
> else
>         to_entries | map(
>                 select(IN(.key;
>                         "foo", "val", "var"
>                 )) | "set -A jsn_\(.key | @sh) -- \(.value | @sh)"
>         ) | .[]
> end' <nil.jsn
null
$ jq -r 'if . == null then
>         null
> else
>         to_entries | map(
>                 select(IN(.key;
>                         "foo", "val", "var"
>                 )) | "set -A jsn_\(.key | @sh) -- \(.value | @sh)"
>         ) | .[]
> end' <test.jsn
set -A jsn_'var' -- 1
set -A jsn_'foo' -- 'bar' 'baz' 'space test'

在这个例子中,只有键 fooval(此处不存在)和 var 是允许的。这可以用来过滤掉值不是简单值或一维简单值的JSONArray的键,以确保结果是安全的¹。
你可以在shell片段中如下使用它:
set -o pipefail
if ! vars=$(curl "${curlopts[@]}" "$url" | jq -r '
        if . == null then
                null
        else
                to_entries | map(
                        select(IN(.key;
                                "foo", "val", "var"
                        )) | "set -A jsn_\(.key | @sh) -- \(.value | @sh)"
                ) | .[]
        end
    '); then
        echo >&2 "E: no API response"
        exit 1
fi
if [[ $vars = null ]]; then
        echo >&2 "E: empty API response"
        exit 1
fi
eval "$vars"
echo "I: API response: var=$jsn_var"
for x in "${jsn_foo[@]}"; do
        echo "N: got foo '$x'"
done

① 当使用jq时,如果不使用问号,它会抛出错误,但是如果确实存在多维数组或其他诡计的话,失败模式就不太友好了。
$ cat test.jsn 
{
  "foo": [
    [
      "bar",
      "baz"
    ],
    "space test"
  ],
  "var": 1,
  "x": "test"
}
$ jq -r 'to_entries? | map("set -A jsn_\(.key|@sh) -- \(.value|@sh)") | .[]' <test.jsn
$ echo $?
0
$ jq -r 'to_entries | map("set -A jsn_\(.key|@sh) -- \(.value|@sh)") | .[]' <test.jsn 
jq: error (at <stdin>:11): array (["bar","baz"]) can not be escaped for shell
$ echo $?
5
$ jq -r 'if . == null then
>         null
> else
>         to_entries | map(
>                 select(IN(.key;
>                         "foo", "val", "var"
>                 )) | "set -A jsn_\(.key | @sh) -- \(.value | @sh)"
>         ) | .[]
> end' <test.jsn
jq: error (at <stdin>:11): array (["bar","baz"]) can not be escaped for shell
$ echo $?
5

确保在不确定JSON是否正确的情况下进行测试。无论如何,都要测试这一点;捕获jq错误退出代码。请注意上面示例中的set -o pipefail(如果shell支持,大多数最新版本都支持),如果cURL或jq失败(或两者都失败),则整个命令替换将失败;否则,您将不得不将cURL输出重定向到临时文件中,检查curl的错误级别,然后在命令替换中对临时文件运行jq并检查其退出状态(如果您希望在错误消息中区分它们,仍然必须执行此操作)。

很遗憾,bash不接受类似于jsn_'var'=(1)这样将引号放在左侧的赋值方式。在允许输出之前,可能需要根据POSIX规则检查变量名(首字符为字母,剩余字符可以是字母、数字或下划线)。 - Charles Duffy
顺便说一下,我经常喜欢采用一种方法,即发出一系列key=val<NUL>序列。在jq中,NUL可以表示为"\u0000";使用jq -j可以抑制自动换行,因此您可以在项目之间添加自己的分隔符,然后使用类似于(在bash中)declare -A vars=(); while IFS= read -r -d '' item; do [[ $item = *=* ]] || continue; vars[${item%%=*}]=${item#*=}; done < <(jq ...)的循环来填充一个数组。(当然,为了保持谨慎,我们需要确保键和值都不包含NUL,并且键不包含=;因此仍然需要进行一些验证)。 - Charles Duffy
哦,这真有趣...我一直在使用ksh语法,并没有检查bash语法,而且它确实不允许引用的LHS@CharlesDuffy,所以对于那个问题,白名单方法是最好的│至于你的NUL分隔方法,最好为此提供一个单独的答案,因为它与这个方法又是分开的(可以通过协处理在Korn shell上工作,而不使用<(jq…)的bashism)。 - mirabilos

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