2

From Json structure, I want a specific dictionary. From key's color yellow or red, I add id value.

[
  {
    "id": "9b058640",
    "type": "db",
    "color": "red",
    "host": "db1"
  },
  {
    "id": "0u858640",
    "type": "db",
    "color": "yellow",
    "host": "db2"
  },
  {
    "id": "0ui9k40",
    "type": "net",
    "color": "red",
    "host": "net1"
  },
  {
    "id": "5ty87a",
    "type": "net",
    "color": "yellow",
    "host": "net2"
  }
]

So I want to get the X dictionary

X=(
   ['yellow']="9b058640 5ty87a"
   ['red']="9b058640 0ui9k40"
 )

I could parse by value :

jq -c '.[] | select(.color | contains("red"))'
0

3 Answers 3

4

No need for ., source, eval or even looping in bash. All you need is declare and jq, which can construct the declaration using escaping with @sh and string interpolation:

declare -A X="($(
  jq -r '
    ("yellow", "red") as $color 
    | @sh "[\($color)]=\(map(select(.color == $color).id) | join(" "))"
  ' input.json
))"
$ echo "${X[yellow]}"
0u858640 5ty87a

$ echo "${X[red]}"
9b058640 0ui9k40
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks you very, it works, it was impossible to do it by myself !!
Excellent answer! jq really is an amazing tool, so well designed / considered - so impressed with it and what can be done with simple bash / jq
1

You can use the @tsv operator in jq to emit tab-separated output, which a bash while read loop can easily parse as input.

Assuming your input JSON is in the variable s:

declare -A X=( )

while IFS=$'\t' read -r color id; do
  X[$color]+="$id "
done < <(jq -r '.[] | [.color, .id] | @tsv' <<<"$s")

The above does take a minor shortcut in that it leaves a trailing space after each item. If that's unacceptable for some reason, you can always go through the array and clean it up after the fact with a second loop:

for color in "${!X[@]}"; do
  X[$color]=${X[$color]%" "}
done

You can see this running in the sandbox at https://replit.com/@CharlesDuffy2/IndigoRemoteEngineering


Alternately, using eval:

#!/usr/bin/env bash
case $BASH_VERSION in ''|[0-3].*) echo "ERROR: bash 4.0+ required" >&2; exit 1;; esac
declare -A X=( )
eval "$(
  jq -r '
    reduce .[] as $item ({}; .[$item.color] += [$item.id])
    | to_entries[]
    | "X[\(.key | @sh)]=\(.value | join(" ") | @sh)"
  '
)" <file.json

2 Comments

Thanks very much for you replying, but I edit the question because of structure of json, because there is comma , and bracket []
In that case just .[] | needs to be added to the query; updating appropriately.
0

Answer:

declare -A a
. <(jq -r '"a[\(.color)]+=${a[\(.color)]+ }\(.id)"' file.json)

Provides:

$ declare -p a
$ declare -A a=([red]="9b058640 0ui9k40" [yellow]="0u858640 5ty87a" )
# or
$ echo "${a[red]}"; echo "${a[yellow]}"
9b058640 0ui9k40
0u858640 5ty87a

Explanation:

  • Construct the following bash code in jq:
  • a[color]+=${a[color]+ }id
  • ${a[color]+ } expands to , only if a[color] is empty.
  • "\(.id)" is jq string interpolation - its replaced with the value of .id
  • use . and a process sub to source this code

6 Comments

Using eval, source or . on generated code introduces security exposure that needs to be mitigated: Use | @sh inside the \( ... ) segments to ensure that hostile IDs or color names can't be used to attack the system's security. You don't want a JSON file saying that something is colored $(rm -rf ~) to delete your home directory.
Thanks very much for you replying, but I edit the question because of structure of json, because there is comma , and bracket []
@CharlesDuffy The issues is escaping .color inside the array subscript. The quotes aren't removed. However a I found a version that works similarly.
@dan, ...I added my own eval-based version that avoids the whitespace issue -- see addendum to other answer.
@dan, see replit.com/@CharlesDuffy2/SphericalWhirlwindDebuggers#main.sh -- yes, there's an error message, but the injected code still gets run. And if you made it $(rm -rf ~; echo hi), then there wouldn't be an error message at all -- the reason there was an error message is that the stdout from rm was empty, and an empty string isn't a valid key.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.