Building json hash from bash associative array

I would like to convert an associative array in bash to json hash / dict. I would prefer to use jq for this, since it is already dependent, and I can rely on it to create well-formed json. Can anyone demonstrate how to do this?

#!/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 }' 
+5
source share
4 answers

There are many possibilities, but given that you have already written a bash for loop, you can start with this version of your script:

 #!/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? // .)) })' 

The result reflects the ordering of the keys created by the bash for loop:

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

In general, a jq-based approach of key-value pairs, with one key per line, followed by the corresponding value on the next line, has a lot to recommend. The following is a general solution to this general pattern, but using NUL as the end of line character.

Keys and values ​​as JSON objects

To make this more general, it would be better to represent keys and values ​​as JSON objects. In this case, we could write:

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

Other options

JSON keys must be JSON strings, so some work may be required to ensure that the required mapping from bash keys to JSON keys is performed. Similar notes apply to mapping bash array values ​​to JSON values. One way to handle arbitrary bash keys would be to let jq perform the conversion:

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

Of course, you could do the same with the values ​​of the bash array, and let jq check if the value can be converted to a number or some other type of JSON as desired (for example, using fromjson? // . ).

Common decision

Here is a general solution on the lines indicated in the jq FAQ, and is protected by @CharlesDuffy. It uses NUL as a separator when passing bash keys and values ​​to jq, and has the advantage of requiring only one jq call. Optionally fromjson? // . filter fromjson? // . fromjson? // . can be omitted or replaced by another.

 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? // .)})' 

Output:

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

This answer is from nico103 on freenode #jq :

 #!/bin/bash declare -A dict=() dict["foo"]=1 dict["bar"]=2 dict["baz"]=3 assoc2json() { declare -nv=$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 
+3
source

It was published and credited to nico103 on the IRC, i.e. for me.

The thing that scares me, of course, is that these associative keys and array values ​​require quotation marks. This is where the launch begins, requiring some extra work to cut off the keys and values:

 function assoc2json { typeset -nv=$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"} $ 

So, now you only need the jq function, which removes the quotation marks, which come in several variants:

  • if the line starts with one quote (ksh), then it ends with one quote, and those should be deleted.
  • if the string starts with a dollar sign and a single quote and ends with a double quote, then they must be deleted, and internal escape back-dumps must be unscreened
  • else leave as-is

I leave this last iterm as an exercise for the reader.

I should note that I use printf here as an iterator!

+1
source

You can initialize the variable with the empty object {} and add the key / values {($key):$value} for each iteration by re-entering the result in the same variable:

 #!/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" 
0
source

Source: https://habr.com/ru/post/1269275/


All Articles