Home > Net >  json dictionary to bash hash table using readarray
json dictionary to bash hash table using readarray

Time:03-08

First a working example with arrays

json_array() {
    local -n array="${1}"
    readarray -d $'\0' -t array < <(
        # Create nul delimited array entry using jq
        jq -cjn --argjson arr "$array" '$arr|map(tostring)|.[] "\u0000"'
    )
}

> unset arr; arr='["a", "b", "c"]'; json_array arr; echo "${arr[0]} ${arr[1]} ${arr[2]}"
a b c

Now I'm trying to do something similar with dict, convert a json dict into a bash associative array

json_dict() {
    local -n dict="${1}"
    declare -A hash_table

    append_to_hash_table() {
        shift
        { read -r key;  read -r value; } <<<"$1"
        hash_table =([$key]="$value")
    }

    readarray -d $'\0' -c 1 -C append_to_hash_table < <(
        # Create nul delimited dict entry using jq
        jq -cjn --argjson d "$dict" '$d|to_entries|map("\(.key)\n\(.value|tostring|@sh)")|.[] "\u0000"'
    )

    # Here hash_table contain the correct output
    dict=""
    dict="$hash_table"
}

> unset arr; arr='{"a": "aa", "l": "bb", "c": "ccccc"}'; json_dict arr; echo "${arr[@]}"
Nothing

It seems dict="$hash_table" doesn't correctly update the refname, How can I make bash dict refname point to hash_table?

CodePudding user response:

There's no need for readarray here: You can have two separate NUL-delimited reads as part of your while loop.

See the below answer demonstrated at https://replit.com/@CharlesDuffy2/GrandioseDraftyArguments#main.sh

while IFS= read -r -d '' key && IFS= read -r -d '' value; do
  hash_table[$key]=$value
done < <(jq -cjn --argjson d "$arr" \
           '$d | to_entries[] | ( .key, "\u0000", .value, "\u0000")')

Putting this into context:

json_dict() {
  declare key value in_value="${!1}"
  unset "$1"                   # FIXME: Better to take a $2 for output variable
  declare -g -A "$1"
  declare -n hash_table="$1"
  while IFS= read -r -d '' key && IFS= read -r -d '' value; do
    hash_table[$key]=$value
  done < <(
    jq -cjn --argjson d "$in_value" \
      '$d | to_entries[] | ( .key, "\u0000", .value, "\u0000")'
  )
}
 
arr='{"a": "aa", "l": "bb", "c": "ccccc"}'
json_dict arr
declare -p arr

...emits as output:

declare -A arr=([a]="aa" [c]="ccccc" [l]="bb" )

That said, to answer the question exactly as-asked, thus using readarray:

json_dict() {
  declare -a pieces=()

  readarray -d '' pieces < <(
    jq -cjn --argjson d "${!1}" \
      '$d | to_entries[] | ( .key, "\u0000", .value, "\u0000")'
  )

  unset "$1"
  declare -g -A "$1"
  declare -n hash_table="$1"
  set -- "${pieces[@]}"

  while (( $# )); do
    hash_table[$1]=$2
    { shift && shift; } || return
  done
}

arr='{"a": "aa", "l": "bb", "c": "ccccc"}'
json_dict arr
declare -p arr

CodePudding user response:

Wouldn't it be simpler to just use declare and have jq create the contents using @sh for shell conformity?

Indexed array:

unset arr; arr='["a", "b", "c"]'
declare -a arr="($(jq -r @sh <<< "$arr"))"

Associative array:

unset arr; arr='{"a": "aa", "l": "bb", "c": "ccccc"}'
declare -A arr="($(jq -r 'to_entries[] | @sh "[\(.key)]=\(.value)"' <<< "$arr"))"
  • Related