Search code examples
jsonbashjq

How to append records to a json object file in bash


I need to loop through a dictionary and append records to a json object file option.json the code I have is

for K in "${!MYMAP[@]}"; do 
    opt="{
        \"OptionName\":\"${K}\",
        \"Value\":\"${MYMAP[$K]}\"
        },"
    echo $opt >> option.json
done 

But this produce option.json as below

{
    "OptionName": "name",
    "Value": "test"
},
{
    "OptionName": "age",
    "Value": "13"
}

How do i make the option.json a json object and appending each {} inside the object, so my option.json would look like below

[
    {
        "OptionName": "name",
        "Value": "test"
    },
    {
        "OptionName": "age",
        "Value": "13"
    }
]

Solution

  • If you omit the trailing comma in the initial production, you get a stream of inputs that jq can collect into an array using the --slurp (or -s) flag:

    for k in "${!mymap[@]}"; do 
      opt="{\"OptionName\":\"${k}\", \"Value\":\"${mymap[$k]}\"}"
      echo "$opt"                            # no comma here --^
    done | jq -s . > option.json
    

    However, you can also have jq do the JSON composition in the first place, guaranteeing valid JSON encoding even with challenging values (such as double quotes, etc.). For example, I'd use the --args option to import the bash array's keys and values as positional parameters, and the $ARGS builtin to access them:

    declare -A mymap=([name]="test" [age]="13")
    
    jq -n '
      $ARGS.positional | [_nwise(length/2)] | transpose
      | map({OptionName: first, Value: last})
    ' --args "${!mymap[@]}" "${mymap[@]}" > option.json
    

    As @oguz pointed out, newer versions of bash can also expand arrays to their keys and values in our desired alternating order by using "${mymap[@]@k}". The full jq filter would then just read:

    jq -n '$ARGS.positional | [_nwise(2) | {OptionName: first, Value: last}]' \
      --args "${mymap[@]@k}"