从bash关联数组构造JSON对象

qzwqbdag  于 2023-03-04  发布在  其他
关注(0)|答案(5)|浏览(153)

我想将bash中的关联数组转换为JSON散列/dict。我更喜欢使用JQ来完成此操作,因为它已经是一个依赖项,我可以依赖它来生成格式良好的json。有人能演示如何实现此操作吗?

#!/bin/bash

declare -A dict=()

dict["foo"]=1
dict["bar"]=2
dict["baz"]=3

for i in "${!dict[@]}"
do
    echo "key  : $i"
    echo "value: ${dict[$i]}"
done

echo 'desired output using jq: { "foo": 1, "bar": 2, "baz": 3 }'
hmmo2u0o

hmmo2u0o1#

有很多种可能性,但是考虑到您已经编写了一个bash for循环,您可能希望从脚本的以下变体开始:

#!/bin/bash
# Requires bash with associative arrays
declare -A dict

dict["foo"]=1
dict["bar"]=2
dict["baz"]=3

for i in "${!dict[@]}"
do
    echo "$i" 
    echo "${dict[$i]}"
done |
jq -n -R 'reduce inputs as $i ({}; . + { ($i): (input|(tonumber? // .)) })'

结果反映了bash for循环生成的密钥的顺序:

{
  "bar": 2,
  "baz": 3,
  "foo": 1
}

一般来说,基于给jq提供键值对的方法,其中一个键在一行上,对应的值在下一行上,有很多值得推荐的地方。下面给出了遵循这种通用方案的通用解决方案,但使用NUL作为“行结束”字符。

作为JSON实体的键和值

为了使上面的代码更通用,最好将键和值表示为JSON实体。在本例中,我们可以编写:

for i in "${!dict[@]}"
do
    echo "\"$i\""
    echo "${dict[$i]}"
done | 
jq -n 'reduce inputs as $i ({}; . + { ($i): input })'

其他变更

JSON键必须是JSON字符串,因此可能需要做一些工作来确保实现从bash键到JSON键的Map。类似的注解也适用于从bash数组值到JSON值的Map。处理任意bash键的一种方法是让jq进行转换:

printf "%s" "$i" | jq -Rs .

当然,您可以对bash数组值执行相同的操作,并让jq检查该值是否可以根据需要转换为数字或其他JSON类型(例如,使用fromjson? // .)。

通用解决方案

下面是一个通用的解决方案沿着于jq FAQ中提到的方法,由@CharlesDuffy提出。它在将bash键和值传递给jq时使用NUL作为分隔符,并且具有只需要一次调用jq的优点。如果需要,可以省略过滤器fromjson? // .或用另一个过滤器替换。

declare -A dict=( [$'foo\naha']=$'a\nb' [bar]=2 [baz]=$'{"x":0}' )

for key in "${!dict[@]}"; do
    printf '%s\0%s\0' "$key" "${dict[$key]}"
done |
jq -Rs '
  split("\u0000")
  | . as $a
  | reduce range(0; length/2) as $i 
      ({}; . + {($a[2*$i]): ($a[2*$i + 1]|fromjson? // .)})'

输出:

{
  "foo\naha": "a\nb",
  "bar": 2,
  "baz": {
    "x": 0
  }
}
h5qlskok

h5qlskok2#

此答案来自freenode#jq上的nico103

#!/bin/bash

declare -A dict=()

dict["foo"]=1
dict["bar"]=2
dict["baz"]=3

assoc2json() {
    declare -n v=$1
    printf '%s\0' "${!v[@]}" "${v[@]}" |
    jq -Rs 'split("\u0000") | . as $v | (length / 2) as $n | reduce range($n) as $idx ({}; .[$v[$idx]]=$v[$idx+$n])'
}

assoc2json dict
nbnkbykc

nbnkbykc3#

你可以将一个变量初始化为一个空对象{},并为每次迭代添加键/值{($key):$value},将结果重新注入同一个变量:

#!/bin/bash

declare -A dict=()

dict["foo"]=1
dict["bar"]=2
dict["baz"]=3

data='{}'

for i in "${!dict[@]}"
do
    data=$(jq -n --arg data "$data" \
                 --arg key "$i"     \
                 --arg value "${dict[$i]}" \
                 '$data | fromjson + { ($key) : ($value | tonumber) }')
done

echo "$data"
pu3pd22g

pu3pd22g4#

这已经被贴出来了,并记入了IRC上的nico 103,也就是说,我。
很自然地,让我感到害怕的是这些关联数组的键和值需要引号,下面是一个开始,需要一些额外的工作来对键和值进行反引号操作:

function assoc2json {
    typeset -n v=$1
    printf '%q\n' "${!v[@]}" "${v[@]}" |
        jq -Rcn '[inputs] |
                . as $v |
                (length / 2) as $n |
                reduce range($n) as $idx ({}; .[$v[$idx]]=$v[$idx+$n])'
}

$ assoc2json a
{"foo\\ bar":"1","b":"bar\\ baz\\\"\\{\\}\\[\\]","c":"$'a\\nb'","d":"1"}
$

所以现在所需要的就是一个jq函数,它可以删除引号,引号有几种类型:

  • 如果字符串以单引号(ksh)开头,则以单引号结尾,需要删除这些单引号
  • 如果字符串以美元符号和单引号开始,以双引号结束,则需要删除这些字符串,并且需要取消内部反斜杠转义
  • 否则保持原样

我把这最后一项留给读者作练习。
我应该注意,我在这里使用printf作为迭代器!

3bygqnnd

3bygqnnd5#

bash 5.2引入了@kparameter transformation,使这一点变得更加容易。例如:

$ declare -A dict=([foo]=1 [bar]=2 [baz]=3)
$ jq -n '[$ARGS.positional | _nwise(2) | {(.[0]): .[1]}] | add' --args "${dict[@]@k}"
{
  "foo": "1",
  "bar": "2",
  "baz": "3"
}

相关问题