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"
}
Answer from jesse_b on Stack ExchangeYou 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 (...).
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"
}
]
Create object from array of keys and values
json - Using jq to extract multiple fields and create a new object - Stack Overflow
linux - creating a nested json file from variables using jq - Unix & Linux Stack Exchange
bash - Add JSON objects to array using jq - Unix & Linux Stack Exchange
Videos
I'd do it in 2 steps: (EDIT: artifacts is an array of objects)
inner=$(jq -n --arg name oer \
--arg version "$ot" \
'$ARGS.named'
)
final=$(jq -n --arg configId "$configid" \
--arg objectname "tempfile" \
--arg test "2021" \
--argjson artifacts "[$inner]" \
'$ARGS.named'
)
echo "$final"
{
"configId": "c8f",
"objectname": "tempfile",
"artifacts": [
{
"name": "oer",
"version": "1.01"
}
], "test": "2021"
}
Add a -c if you want the final output to be one line.
The safest way to create JSON on the command line is through using a tool that constructs it for you as jq does. However, since you have a nested structure, you may want to create that sub-structure in a separate call to jq as is shown by glenn jackman.
Another way to do what they show, but in one go:
jq -n \
--arg configId "$configid" \
--arg objectname tempfile \
--argjson artifacts "$(
jq -n \
--arg name oer \
--arg version "$ot" \
'$ARGS.named'
)" \
--arg test 2021 \
'$ARGS.named'
We use --argjson rather than --arg to include the JSON document from the inner jq, as it is a JSON document and not a string that needs encoding.
You could also use jo, which may reduce typing a bit,
jo configId="$configid" \
objectname=tempfile \
artifacts="$( jo name=oer version="$ot" )" \
test=2021
A description of the jo utility is found here: https://jpmens.net/2016/03/05/a-shell-command-to-create-json-jo/
Assuming a ticket is to be generated for each object entry:
{tickets: [
.objectEntries[]
| [.attributes[]
| [.objectTypeAttributeId,
(.objectAttributeValues | map(.displayValue))] as [$id, $val]
| if $id == 328 then {ticketId: $val[0]}
elif $id == 329 then {hostnames: $val}
elif $id == 330 then {date: $val[0]}
else empty end
] | add
]}
Online demo
Here we go, it's not pretty, there may be a better solution but it works: https://jqplay.org/s/sxussfa2Vj
.objectEntries | {tickets: map(.attributes |
{ticketID: (reduce .[] as $r (null; if $r.objectTypeAttributeId == 328
then $r.objectAttributeValues[0].value else . end)),
date: (reduce .[] as $r (null; if $r.objectTypeAttributeId == 330
then $r.objectAttributeValues[0].value else . end)),
hostnames: (reduce .[] as $r ([]; if $r.objectTypeAttributeId == 329
then $r.objectAttributeValues | map(.value) else . end))})}
There's a lot of unpacking and repacking going on here that sort of distracts from the core. You have an array of tickets (aka entries), and over those we map. The various properties we have to grab from different entries of an array, which is done using reduce. Reduce goes through the array of objects and picks out the right one and keeps track of the value.
Maybe there's a nice way, but this works already, so you can play with it further, trying to simplify.
Your original solution almost works, you did a good job there, just needed a map:
.objectEntries[].attributes |
{ticketid: . | map(select(.objectTypeAttributeId == 328))[0] |
.objectAttributeValues[0].displayValue,
date: . | map(select(.objectTypeAttributeId == 330))[0] |
.objectAttributeValues[0].displayValue,
hostnames: . | map(select(.objectTypeAttributeId == 329))[0] |
[.objectAttributeValues[].displayValue]}
Try it out, it even works with multiple tickets ;) https://jqplay.org/s/ydoCgv9vsI
This trick with the jq 1.5 inputs streaming filter seems to do it
... | jq -n '.items |= [inputs]'
Ex.
$ find ~/ -maxdepth 1 -name "D*" |
while read line; do
jq -n --arg name "$(basename "$line")" \
--arg path "$line" \
'{name: $name, path: $path}'
done | jq -n '.items |= [inputs]'
{
"items": [
{
"name": "Downloads",
"path": "/home/steeldriver/Downloads"
},
{
"name": "Desktop",
"path": "/home/steeldriver/Desktop"
},
{
"name": "Documents",
"path": "/home/steeldriver/Documents"
}
]
}
Calling jq directly from find, and then collecting the resulting data with jq to construct the final output, without any shell loops:
find ~ -maxdepth 1 -name '[[:upper:]]*' \
-exec jq -n --arg path {} '{ name: ($path|sub(".*/"; "")), path: $path }' \; |
jq -n -s '{ items: inputs }'
The jq that is being executed via -exec creates a JSON object per found pathname. It strips off everything in the pathname up to the last slash for the name value, and uses the pathname as is for the path value.
The final jq reads the data from find into an array with -s, and simply inserts it as the items array in a new JSON object. The final jq invocation could also be written jq -n '{ items: [inputs] }.
Example result (note that I was using [[:upper:]* in place of D* for the -name pattern with find):
{
"items": [
{
"name": "Documents",
"path": "/home/myself/Documents"
},
{
"name": "Mail",
"path": "/home/myself/Mail"
},
{
"name": "Work",
"path": "/home/myself/Work"
}
]
}
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.