2

I'm looking to build object using jq and add object key to dynamic key and I could not figure out. Here is my sample script:

#!/bin/bash -e

environments=('development' 'stage' 'production')
regions=('us-east-1' 'us-west-2')

tree='{}'

for environment in "${environments[@]}"
do
    echo "${environment}"
    # Or do something else with environment

    tree="$(jq --arg jqEnvironment "${environment}" '. | .[$jqEnvironment] = {}' <<< "${tree}")"

    for region in "${regions[@]}"
    do
        echo "${region}"
        # Or do something with region

        tree="$(jq --arg jqEnvironment "${environment}" --arg jqRegion "${region}" '. | .[$jqEnvironment] | .[$jqRegion] = {}' <<< "${tree}")"
    done
done

jq . <<< "${tree}"

actual outputs

{
  "us-west-2": {}
}

But what I want is this

{
  "development": {
    "us-east-1": {},
    "us-west-2": {}
  },
  "stage": {
    "us-east-1": {},
    "us-west-2": {}
  },
  "production": {
    "us-east-1": {},
    "us-west-2": {}
  }
}

I could not figure out, please help!

Nam Nguyen
  • 5,668
  • 14
  • 56
  • 70

1 Answers1

4

The following script produces the desired result, and should be fairly robust:

#!/bin/bash

environments=('development' 'stage' 'production')
regions=('us-east-1' 'us-west-2')

jq -n --slurpfile e <(for e in "${environments[@]}" ; do echo "\"$e\""; done) \
   --slurpfile r <(for r in "${regions[@]}" ; do echo "\"$r\"" ; done) \
   '($r | map ({(.): {}}) | add) as $regions
     | [{($e[]): $regions}] | add'

The main point to note here is that in order to construct an object with a dynamically determined key, one has to use parentheses, as in {(KEY): VALUE}

Of course, if the values for the "environments" and "regions" were available in a more convenient form, the above could be simplified.

Output

{
  "development": {
    "us-east-1": {},
    "us-west-2": {}
  },
  "stage": {
    "us-east-1": {},
    "us-west-2": {}
  },
  "production": {
    "us-east-1": {},
    "us-west-2": {}
  }
}
peak
  • 105,803
  • 17
  • 152
  • 177
  • Thanks @peak, I ran the script and it does not look like it output valid json format though { "development": { "us-east-1": {}, "us-west-2": {} } } { "stage": { "us-east-1": {}, "us-west-2": {} } } { "production": { "us-east-1": {}, "us-west-2": {} } } – Nam Nguyen Jan 30 '18 at 04:06
  • is it possible to keep 2 for-loop separated because I need to do something with environment and region variables? Your short form solution is pretty cool but not flexible for me to use environment or region variables for other actions. Thanks a lot for your solution. – Nam Nguyen Jan 30 '18 at 04:12
  • --argfile variable-name filename: Do not use. Use --slurpfile instead. (This option is like --slurpfile, but when the file has just one text, then that is used, else an array of texts is used as in --slurpfile.) – Nam Nguyen Jan 30 '18 at 04:17
  • 1. I tweaked the script to produce a single JSON object. 2. As you say, --argfile has been deprecated but it is perfectly fine here, and has the (possible) advantage of working in versions of jq that don't have --slurpfile. – peak Jan 30 '18 at 04:30
  • look like you updated and it caused errors now "jq: error: syntax error, unexpected '[', expecting '|' (Unix shell quoting issues?) at , line 2: [{($e[]): $regions}] | add jq: 1 compile error" – Nam Nguyen Jan 30 '18 at 04:31
  • do you know a possible way to use 2 separated for-loop? Thanks for your helps. – Nam Nguyen Jan 30 '18 at 04:34
  • 1. Yes, there was a typo. Sorry about that. Fixed. 2. There are two for-loops, so I'm not sure what you're looking for, but I think you have enough to work with now. – peak Jan 30 '18 at 04:34
  • This is brilliantly done ++. Add more explanation if time permits. – sjsam Jan 30 '18 at 05:15