Modify one value in array - arrays

This is VERY similar to Update one value in array of dicts, using jq
I have a foo.json and I want to update AAA to AAA-MY-SUFFIX.
Basically, I want to get the current value (AAA), and then add a suffix to it.
[
{
"Key": "Name",
"Value": "awesome"
},
{
"Key": "role",
"Value": "AAA"
}
]
From the previous question, I can REPLACE the value of AAA using this:
cat foo.json | jq '(.[] | select(.Key == "role") | .Value) |= "-MY_SUFFIX"'
But I want to APPEND a suffix to the existing value, not completely replace it.
Something like this (but it doesn't work, of course):
cat tags.json | jq '(.[] | select(.Key == "role") | .Value) |= .Value + "-MY_SUFFIX"'
I feel I'm SO close, but I just can figure it out :(

Close indeed. You could simply replace .Value + "-MY_SUFFIX" by
. + "-MY_SUFFIX"
Or better yet, use +=, as in: ... += "-MY_SUFFIX"
Personally, I'd use the filter:
map(if .Key == "role" then .Value += "-MY_SUFFIX" else . end)
(Actually, the stated requirements would accord better with using the suffix "-MY-SUFFIX" :-)

After much fooling around, I think I got it:
cat tags.json | jq '(.[] | select(.Key == "role") | .Value) |= (. + "- MY_SUFFIX")'

Related

jq condensing sub array permutation query

I intend to extract a csv with a row for each sub array item.
Given a json array with a sub array. e.g. like this one:
[
{
"foo": 108,
"bar": ["a","b"]
},
{
"foo": 201,
"bar": ["c","d"]
}
]
It is possible to fetch the data by utilizing an intermediate object.
.[] | { "y": .foo, "x": .bar[] }| [.y,.x] | #csv
https://jqplay.org/s/922RlkbFNA
But I'd like to express it in a less elaborate form.
However the following does not work :( :
.[] | [ (.foo, .bar[]) ] | #csv
PS: I struggle to find a fitting headline
In three lines:
.[]
| [.foo] + (.bar[]|[.])
| #csv
or maybe less obscurely:
.[]
| .bar[] as $bar
| [.foo, $bar]
| #csv

jq try .. catch goes directly to catch even when no error

So without try and catch in my jq line everything goes well.
Thing is I need the try and catch because this example works, but not for all input:
> cat movies.json | jq '.[3]' |
jq '.release_date |= if . == null or . == ""
then .
else (. | strptime("%Y-%m-%d") | mktime) end'
{
"id": "166428",
"title": "How to Train Your Dragon: The Hidden World",
"poster": "https://image.tmdb.org/t/p/w1280/xvx4Yhf0DVH8G4LzNISpMfFBDy2.jpg",
"overview": "As Hiccup fulfills his dream of creating a peaceful dragon utopia, Toothless’ discovery of an untamed, elusive mate draws the Night Fury away. When danger mounts at home and Hiccup’s reign as village chief is tested, both dragon and rider must make impossible decisions to save their kind.",
"release_date": 1546473600
}
When I add try and catch I get this:
> cat movies.json | jq '.[3]' |
jq '.release_date |= try (if . == null or . == ""
then .
else (. | strptime("%Y-%m-%d") | mktime) end)
catch (.)'
{
"id": "166428",
"title": "How to Train Your Dragon: The Hidden World",
"poster": "https://image.tmdb.org/t/p/w1280/xvx4Yhf0DVH8G4LzNISpMfFBDy2.jpg",
"overview": "As Hiccup fulfills his dream of creating a peaceful dragon utopia, Toothless’ discovery of an untamed, elusive mate draws the Night Fury away. When danger mounts at home and Hiccup’s reign as village chief is tested, both dragon and rider must make impossible decisions to save their kind.",
"release_date": {
"__jq": 0
}
}
This is my version:
> jq --version
jq-1.6
In the end I want to get something like this working:
> cat movies.json |
jq 'map_values(try (.release_date |= if . == null or . == ""
then .
else (. | strptime("%Y-%m-%d") | mktime) end)
catch (.))'
Update: It appears you discovered a bug.
Some parts of your problem statement are unclear to me:
You special-case null and "", but if . is otherwise unparseable, you let strptime/1 error and presumably want to return the unparseable input (via catch (.), which has a subtlety to it). Why not just try and parse the date and fall back to the unparseable input regardless of special cases?
If you do intend to create a special-case, why not let this be if type == "string"? This is, after all, the only meaningful type to feed strptime/1 (even if it might not contain a parseable date).
When defaulting to the unparseable input, the output type/schema becomes unpredictable, but perhaps this is okay for you.
The "In the end" part suggests, by inlining .release_date within try, that this field may be optional. I don't know if you intended to signify that this may be the case, so I'm choosing to go with the assumption that it's not going to be, since it isn't specified.
jq 1.5
Here's a simplified example, some object properties removed:
$ jq -c '.[]' release_date.json
{"id":"42","release_date":"2019-12-31"}
{"id":"42","release_date":null}
{"id":"42","release_date":"SOON!"}
$ jq 'map(.release_date |= . as $date | try (strptime("%Y-%m-%d") | mktime) catch $date)' release_date.json
[
{
"id": "42",
"release_date": 1577750400
},
{
"id": "42",
"release_date": null
},
{
"id": "42",
"release_date": "SOON!"
}
]
In the catch block, . refers to the stringified exception, so to default to the unparseable value, it is temporarily referred to as $date. Or using a user-defined function with a $value argument:
$ jq 'def parsedate($date):
try ($date | strptime("%Y-%m-%d") | mktime)
catch $date;
map(.release_date |= parsedate(.))' release_date.json
jq 1.6
Interestingly, the solutions above don't work in jq 1.6.
I've tried to narrow the discrepancy down to a minimum.
https://jqplay.org/s/M_RpdNHvHF:
$ jq-1.6 '. |= try . catch .' <<< 1
{
"__jq": 0
}
Until this unintended behavior changes, avoiding |= and try-catch together is a viable option:
https://jqplay.org/s/ki8I1YnU56:
$ jq 'map(.release_date = (.release_date | . as $date
| try (strptime("%Y-%m-%d") | mktime) catch $date))' release_date.json
https://jqplay.org/s/i4FJPpXEG0:
$ jq 'def parsedate($date):
try ($date | strptime("%Y-%m-%d") | mktime)
catch $date;
map(.release_date = parsedate(.release_date))' release_date.json
I've reported it here.
In brief, jq 1.6 introduced a bug affecting the handling of catch/try in the context of |=.
The simple workaround in the present case is to avoid |= e.g. by:
(.release_date
| try (if . == null or . == ""
then .
else strptime("%Y-%m-%d") | mktime end)
catch .) as $r
| .release_date = $r'
Notice there is no need for an initial . | in the else clause.

Find an entry in a JSON list of objects based on object's key

I've got JSON that, among other top-level content, includes the following:
{
"organizationStructure": [
{
"id": 212119,
"key": "level2"
},
{
"id": 212112,
"key": "level1"
}
]
}
How can I filter by the key to find only a given id (such as that for "level2")?
Or, to keep it simpler (or more complicated, depending on your point of view):
jq '.organizationalStructure[] |
select(.key == "level1") | {id: .id}'
Often, it's nice to clean up the output:
jq -r '.organizationalStructure[] |
select(.key == "level1") | {id: .id}.id'
As per PesaThe's suggestion in the comments, this can be simplified to:
jq -r '.organizationalStructure[] |
select(.key == "level1").id'
and {.id: id} can be written simply {id}
This can be done using select. Note if there is more than one item with the key of level2 this will only return the first:
.organizationalStructure | map(select(.key == "level2") | .id)[0]

jq - How to concatenate an array in json

Struggling with formatting of data in jq. I have 2 issues.
Need to take the last array .rental_methods and concatenate them into 1 line, colon separated.
#csv doesn't seem to work with my query. I get the error string ("5343") cannot be csv-formatted, only array
jq command is this (without the | #csv)
jq --arg LOC "$LOC" '.last_updated as $lu | .data[]|.[]| $lu, .station_id, .name, .region_id, .address, .rental_methods[]'
JSON:
{
"last_updated": 1539122087,
"ttl": 60,
"data": {
"stations": [{
"station_id": "5343",
"name": "Lot",
"region_id": "461",
"address": "Austin",
"rental_methods": [
"KEY",
"APPLEPAY",
"ANDROIDPAY",
"TRANSITCARD",
"ACCOUNTNUMBER",
"PHONE"
]
}
]
}
}
I'd like the output to end up as:
1539122087,5343,Lot,461,Austin,KEY:APPLEPAY:ANDROIDPAY:TRANSITCARD:ACCOUNTNUMBER:PHONE:,
Using #csv:
jq -r '.last_updated as $lu
| .data[][]
| [$lu, .station_id, .name, .region_id, .address, (.rental_methods | join(":")) ]
| #csv'
What you were probably missing with #csv before was an array constructor around the list of things you wanted in the CSV record.
You could repair your jq filter as follows:
.last_updated as $lu
| .data[][]
| [$lu, .station_id, .name, .region_id, .address,
(.rental_methods | join(":"))]
| #csv
With your JSON, this would produce:
1539122087,"5343","Lot","461","Austin","KEY:APPLEPAY:ANDROIDPAY:TRANSITCARD:ACCOUNTNUMBER:PHONE"
... which is not quite what you've said you want. Changing the last line to:
map(tostring) | join(",")
results in:
1539122087,5343,Lot,461,Austin,KEY:APPLEPAY:ANDROIDPAY:TRANSITCARD:ACCOUNTNUMBER:PHONE
This is exactly what you've indicated you want except for the terminating punctuation, which you can easily add (e.g. by appending + "," to the program above) if so desired.

JQ: Finding array index of object with specific attribute value

I have the following:
{"arr":[{"Name":"web"},{"Name":"app"}]}
I need to find out the array index of all array objects that do not have "app" in the attribute "Name".
I've tried to use a combination of "select" and "keys" but this does not work:
jq '.arr[] | select(.Name != "app") | keys'
Here's one of many possibilities:
.arr | range(0;length) as $i | select(.[$i].Name != "app") | $i
And slightly more briefly but less efficiently:
.arr | to_entries[] | select(.value.Name != "app") | .key
And if you are itching to use a for-style loop, or if you want something to think about:
foreach .arr[] as $o (-1; .+1; select($o.Name != "app"))
Here is a solution which uses tostream.
tostream
| if .[0][-1] == "Name" and .[1] == "app"
then .[0][-2]
else empty
end

Resources