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) \
'(
regions
| [{(
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": {}
}
}
Answer from peak on Stack Overflowadd object to dynamic key
json - How to use jq to create an object with an arbitrary key from a sub array? - Stack Overflow
text processing - jq create object with property name from variable - Unix & Linux Stack Exchange
Construct JSON with a variable key using jq - Stack Overflow
Is it possible to do something like this:
jq -r --argjson i 0 --arg key "name" '.projects[$i].$key' input-file
I've tried enclosing $key in both parenthesis and square brackets unsuccessfully.
This was actually relatively straight forward:
.things | .[] | {name: .name, category: .params | .[] | select(.key=="category") | .value }
Your params almost looks like key/value entries, so you could create an object out of them by passing the array to from_entries. So to combine everything, you merely need to do this:
.things | map({name} + (.params | from_entries))
This yields:
[
{
"name": "foo",
"key1": "val1",
"category": "thefoocategory"
},
{
"name": "bar",
"key1": "val1",
"category": "thebarcategory"
}
]
You can accomplish this by wrapping $n in parenthesis to tell jq to evaluate the expression:
n="foo"; echo "{}" | jq --arg n "$n" '. += { (
n }'
Or probably better suited for this task would be jo(1):
$ jo "$n"="$n"
{"foo":"foo"}
I'm not sure if the documentation is just wrong or if my ability to comprehend is not at a high enough level but it does seem to say that your example should work:
Key expressions other than constant literals, identifiers, or variable references, need to be parenthesized, e.g., {("a"+"b"):59}.
And one might assume that is referring to a jq native variable rather than one injected by the shell as they are slightly different, but alas both need to be parenthesized:
$ echo '{}' | jq 'def myvar: "foo"; {myvar: myvar}'
{
"myvar": "foo"
}
$ echo '{}' | jq 'def myvar: "foo"; {(myvar): myvar}'
{
"foo": "foo"
}
You can use:
jq -cn --arg n "$n" '{
n}'
{"foo":"foo"}
That is use the variable $n as the key, not the "$n" string. jq -n is like echo null | jq.
$ jq -cn --arg n "$n" '.[
n'
{"foo":"foo"}
Not using the {...} object constructor but assigning a value for a given key.
$ jq -cn --arg n "$n" '{"key\($n)":$n}'
{"keyfoo":"foo"}
Showing how to dereference a jq variable inside a string literal (in \(expr), expr can be any jq expression, not just a variable).
$ jq -cn --arg n "$n" '{("key"+
n}'
{"keyfoo":"foo"}
In an object constructor, when keys are to be the result of a jq expression, they must be inside (...).
Use parentheses to evaluate $key early as in:
jq --null-input --arg key foobar '{($key): "value"}'
See also: Parentheses in JQ for .key
You can also use String interpolation in jq which is of the form "\(..)". Inside the string, you can put an expression inside parens after a backslash. Whatever the expression returns will be interpolated into the string.
You can do below. The contents of the variable key is expanded and returned as a string by the interpolation sequence.
jq --null-input --arg key foobar '{ "\($key)": "value"}'
Elements of a stream are processed independently. So we have to change the input.
We could group the stream elements into an array. For an input stream, this can be achieved using --slurp/-s.[1]
jq -s '
( .[0].Columns[0] | map_values( tostring ) ) as $map |
(
.[0],
(
.[1:][] |
.Users[] |= with_entries(
.key = $map[ .key ]
)
)
)
'
Demo on jqplay
Alternatively, we could use --null-input/-n in conjunction with input and/or inputs to read the input.
jq -n '
input |
( .Columns[0] | map_values( tostring ) ) as $map |
(
.,
(
inputs |
.Users[] |= with_entries(
.key = $map[ .key ]
)
)
)
'
Demo on jqplay
Note that your desired output isn't valid JSON. Object keys must be strings. So the above produces a slightly different document than requested.
Note that I assumed that .Columns is always an array of one exactly one element. This is a nonsense assumption, but it's the only way the question makes sense.
- For a stream the code generates, you could place the stream generator in an array constructor (
[]).reducecan also be used to collect from a stream. For example,map( ... )can be written as[ .[] | ... ]and asreduce .[] as $_ ( []; . + [ $_ | ... ] ).
The following has the merit of simplicity, though it does not sort the keys. It assumes jq is invoked with the -n option and of course produces a stream of valid JSON objects:
input
| . as $Columns
| .Columns[0] as $dict
| input # Users
| .Users[] |= with_entries(.key |= ($dict[.]|tostring))
| $Columns, .
If having the keys sorted is important, then you could easily add suitable code to do that; alternatively, if you don't mind having the keys of all objects sorted, you could use the -S command-line option.
You could tweak your attempt as follows:
jq -s 'map({ (.id) : . }) | add' <data
However, it would be more efficient to use inputs and reduce with the -n command-line option instead of -s.
Of course, using this approach runs the risk of collisions.
You might also want to add del(.id)
Ah! I've got it! Or I've got one solution - please post if there's a better way.
jq -s '[group_by(.id)[]| add | { (.id) : . } ]|add' <data
https://jqplay.org/s/BfAdRBZUMW
group_bygroups the inputs by their.idvalue and produces an array of arrays - the inner arrays are the values that match on id.for each group the inner arrays are passed to
addwhich, because the things in the inner arrays are objects, merges them.That leaves a 2 item array. We feed that to an object constructor which plucks the
idas the key and the whole item as the value. This still leaves an array of items.the outer
[](starts at start of pattern) says take all those and feed it toadd(again), which merges the final objects created in (3).
It works, but there may be a cleaner way.
EDIT
This is uglier but produces the same result and is ~24% faster on a 9MB dataset.
jq -s 'reduce [.[]|{ (.id) : . }][] as $item ({}; . * $item )' <data
This uses reduce <list> as <$var> (<initiation>; <iteration>) starting with an empty object {} and using the merge operator * starting from the incoming item . to create the output. I'm surprised it's faster, but I understand that group_by does a sort, so I guess that's an additional time cost.
Like this:
$ jq '.compilerOptions.skipLibCheck=true' file.json
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"skipLibCheck": true
}
}
The easiest way to add the key with its value has been covered in another answer. That answer adds the key to the end of the list of keys in the compilerOptions object. Normally, the ordering of the keys does not matter, and if you need things ordered in a particular way, you will use an array. However, I'm noticing that you (for whatever reason) expect the key to be added first, before the existing baseUrl key.
We can add the key in that position by, instead of adding the new key to the existing object, instead, add the existing object's keys to the end of the new key. So given the existing JSON document,
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"jerry": "Was a race car driver"
}
}
... we may want to use
jq --argjson skipLibCheck true '.compilerOptions = $ARGS.named + .compilerOptions' file
Given our example document above, this would generate
{
"compileOnSave": false,
"compilerOptions": {
"skipLibCheck": true,
"baseUrl": "./",
"jerry": "Was a race car driver"
}
}
The $ARGS.named thing is an object which contains the key-value pairs defined with --arg and/or --argjson on the command line. In the example above, this would be {"skipLibCheck":true}. Note that the $ARGS feature was introduced after release 1.5 of jq.
With the older 1.5 release of jq, you may use
jq --argjson skipLibCheck true '.compilerOptions = { skipLibCheck: $skipLibCheck } + .compilerOptions' file
Use --arg instead of --argjson if you want the value to be the string true rather than the special boolean value true.
The following gives an alternative way of adding the key at the end (to what's mentioned in the other answer), which follows the same pattern as the above command. Note that I'm also switching to using --arg here to insert true as a string, just to show how that looks.
jq --arg skipLibCheck true '.compilerOptions += $ARGS.named' file
... which would give you
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"jerry": "Was a race car driver",
"skipLibCheck": "true"
}
}
With the older 1.5 release of jq, you may use
jq --arg skipLibCheck true '.compilerOptions += { skipLibCheck: $skipLibCheck }' file