Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constructing a json hash from a bash associative array

Tags:

json

bash

jq

I would like to convert an associative array in bash to a json hash/dict. I would prefer to use jq to do this as it is already a dependency and I can rely on it to produce well formed json. Could someone demonstrate how to achieve 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 }'
like image 997
htaccess Avatar asked Jun 28 '17 01:06

htaccess


People also ask

What is the command to create an associative array in bash?

An associative array in bash is declared by using the declare -A command (How surprising, I know :D). The variable inside the brackets - [] will be the KEY and the value after the equal sign will be the VALUE.

How to echo associative array in bash?

The values of an associative array are accessed using the following syntax ${ARRAY[@]} . To access the keys of an associative array in bash you need to use an exclamation point right before the name of the array: ${! ARRAY[@]} .

Is JSON associative array?

The json_decode() function has a second parameter, and when set to true, JSON objects are decoded into associative arrays.

What is an associative array bash?

Bash, however, includes the ability to create associative arrays, and it treats these arrays the same as any other array. An associative array lets you create lists of key and value pairs, instead of just numbered values. You can assign values to arbitrary keys: $ declare -A userdata. $ userdata[name]=seth.


2 Answers

There are many possibilities, but given that you already have written a bash for loop, you might like to begin with this variation 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 keys produced by the bash for loop:

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

In general, the approach based on feeding jq the key-value pairs, with one key on a line followed by the corresponding value on the next line, has much to recommend it. A generic solution following this general scheme, but using NUL as the "line-end" character, is given below.

Keys and Values as JSON Entities

To make the above more generic, it would be better to present the keys and values as JSON entities. In the present case, we could write:

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

Other Variations

JSON keys must be JSON strings, so it may take some work to ensure that the desired mapping from bash keys to JSON keys is implemented. Similar remarks apply to the mapping from bash array values to JSON values. One way to handle arbitrary bash keys would be to let jq do the conversion:

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

You could of course do the same thing with the bash array values, and let jq check whether the value can be converted to a number or to some other JSON type as desired (e.g. using fromjson? // .).

A Generic Solution

Here is a generic solution along the lines mentioned in the jq FAQ and advocated by @CharlesDuffy. It uses NUL as the delimiter when passing the bash keys and values to jq, and has the advantage of only requiring one call to jq. If desired, the filter fromjson? // . can be omitted or replaced by another one.

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
  }
}
like image 133
peak Avatar answered Nov 09 '22 04:11

peak


This answer is from nico103 on freenode #jq:

#!/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
like image 23
htaccess Avatar answered Nov 09 '22 04:11

htaccess