从 bash 变量和关联数组创建 json

create json from bash variable and associative array

假设我在 bash 中声明了以下内容:

mcD="had_a_farm"
eei="eeieeio"
declare -A animals=( ["duck"]="quack_quack" ["cow"]="moo_moo" ["pig"]="oink_oink" )

我想要以下 json:

{
  "oldMcD": "had a farm",
  "eei": "eeieeio",
  "onThisFarm":[
    {
      "duck": "quack_quack",
      "cow": "moo_moo",
      "pig": "oink_oink"
    }
  ]
}

现在我知道我可以使用 echo、printf 或将文本分配给变量来完成此操作,但让我们假设 animals 实际上非常大,这样做会很麻烦。我还可以循环遍历我的变量和关联数组,并在这样做时创建一个变量。我可以编写这些解决方案中的任何一个,但两者看起来都像是 “错误的方式”。更不用说处理 animals 中的最后一项令人讨厌了,之后我不想要一个“,”。

我认为正确的解决方案是使用 jq,但我很难找到很多关于如何使用此工具编写 jsons(尤其是嵌套的)的文档和示例而不是解析它们。

这是我想出的:

jq -n --arg mcD "$mcD" --arg eei "$eei" --arg duck "${animals['duck']}" --arg cow "${animals['cow']}" --arg pig "${animals['pig']}" '{onThisFarm:[ { pig: $pig, cow: $cow, duck: $duck } ], eei: $eei, oldMcD: $mcD }'

生成所需的结果。实际上,我并不真正关心 json 中键的顺序,但 jq 的输入必须倒退才能按所需顺序获得它,这仍然很烦人。无论如何,这个解决方案很笨重,而且并不比简单地声明一个看起来像 json 的字符串变量更容易编写(并且对于更大的关联数组是不可能的)。如何以高效、合乎逻辑的方式构建这样的 json?

谢谢!

您可以使用 for 循环减少关联数组并将其通过管道传递给 jq:

for i in "${!animals[@]}"; do
    echo "$i"
    echo "${animals[$i]}"
done |
jq -n -R --arg mcD "$mcD" --arg eei "$eei" 'reduce inputs as $i ({onThisFarm: [], mcD: $mcD, eei: $eei}; .onThisFarm[0] += {($i): (input | tonumber ? // .)})'

假设“animals”数组中的none个键或值包含换行符:

for i in "${!animals[@]}"
do
  printf "%s\n%s\n"  "${i}" "${animals[$i]}"
done | jq -nR --arg oldMcD "$mcD" --arg eei "$eei" '
  def to_o:
    . as $in
    | reduce range(0;length;2) as $i ({}; 
        .[$in[$i]]= $in[$i+1]);

  {$oldMcD, 
   $eei,
   onthisfarm: [inputs] | to_o}
'

注意 {$x} 实际上扩展为 {(x): $x}

的技巧

使用“\u0000”作为分隔符

如果任何键或值包含换行符,您可以调整上面的内容,以便使用“\u0000”作为分隔符:

for i in "${!animals[@]}"
do
    printf "%s[=11=]%s[=11=]"  "${i}"  "${animals[$i]}"
done | jq -sR --arg oldMcD "$mcD" --arg eei "$eei" '
 def to_o:
   . as $in
   | reduce range(0;length;2) as $i ({};
       .[$in[$i]]= $in[$i+1]);

  {$oldMcD, 
   $eei,
   onthisfarm: split("\u0000") | to_o }
'

注:以上假设jq版本为1.5以上