Using jq to select a parent by searching child key/value pair - json

Using jq, how do I select a parent object if it contains a child object that meets two filter requirements?
In this example I want to select all Subnets elements that have a child tag with key "Name" and value "TheName". My example has two subnets. The first has "TheName" in the wrong key. The second subnet has the name/value pair I am looking for. i.e. "Key": "Name", "Value": "TheName"
The following selects a subnet with the specified value in one of the tags but not the pair. It returns both subnets instead of only the second subnet.
jq '.Subnets[] | select(.Tags[].Value=="TheName")' output
How do I use jq to select only the subnets that have the name/value pair I am looking for?
{
"Subnets": [
{
"VpcId": "vpc-12345678",
"SubnetId": "subnet-1234567a",
"Tags": [
{
"Key": "IgnoreThis",
"Value": "TheName"
},
{
"Key": "Name",
"Value": "NotTheName"
}
]
},
{
"VpcId": "vpc-12345678",
"SubnetId": "subnet-1234567b",
"Tags": [
{
"Key": "IgnoreThis",
"Value": "ignore"
},
{
"Key": "Name",
"Value": "TheName"
}
]
}
]
}
The desired output would be:
{
"VpcId": "vpc-12345678",
"SubnetId": "subnet-1234567b",
"Tags": [
{
"Key": "IgnoreThis",
"Value": "ignore"
},
{
"Key": "Name",
"Value": "TheName"
}
]
}

Assuming your jq has any/2, a simple and efficient solution would be:
.Subnets[]
| select( any (.Tags[]; .Key == "Name" and .Value == "TheName") )
This produces the output you want, so I won't repeat it here.
If your jq does not have any/2, I'd suggest upgrading, but if that's inconvenient or not an option, you could use this def:
def any(f;g): reduce f as $i (false; . or ($i|g));
p.s. any(str; cond) can be read as: 'Is there any element, e, in the stream, str, such that e|cond has a value other than null or false?'

Here is a solution which uses indices
.Subnets[] | select(.Tags | indices({Key:"Name", Value:"TheName"}) != [])

Related

Delete json array elements based on values of subarray

I have the following json file which contains this array structure:
{
"outer": [
{
"inner": [
{
"value": "val1"
},
{
"value": "val3"
}
]
},
{
"inner": [
{
"value": "val2"
},
{
"value": "val1"
}
]
},
{
"inner": [
{
"value": "val2"
},
{
"value": "val1"
},
{
"value": "val3"
}
]
}
]
}
I want to delete the inner array from the outer array whose elements have specific values and and is of certain length. E.g., if I want to delete the inner array which contains values "val1" and "val2" the result should be:
{
"outer": [
{
"inner": [
{
"value": "val1"
},
{
"value": "val3"
}
]
},
{
"inner": [
{
"value": "val2"
},
{
"value": "val1"
},
{
"value": "val3"
}
]
}
]
}
I have tried
jq 'del( .outer[]|select(.inner[0].value == "val1"))'
but I do not know how to check for the second condition, the length and on top of that the values may appear in any order.
The jq filter you are looking for is:
del(.outer[] | select(.inner | map(.value) | sort == ["val1", "val2"]))
.inner | map(.value) produces an array that contains the values associated to the value key from all objects contained by .inner.
sort is needed because == does a one-to-one comparison of arrays. This way it matches the objects contained in .inner no matter their order. Of course, you have to use a sorted array on the right-hand side (i.e. ["val1", "val2"] and not ["val2", "val1"]).
See it in action.
Here is a solution which should work on all versions of jq at least from version 1.3 onwards, and which is readily adapted to take into account additional criteria, as mentioned in the Q:
# A helper function for defining the retention criteria.
# It is assumed that the input is the array to be checked and that
# `match` is already sorted.
def retain( match ): (map(.value) | sort) != match;
.outer |= map( select( .inner | retain( ["val1", "val2"] ) ))

How to bypass selectattr with "equalto" filter error when the there is no element verifying the condifiton?

I am looping over a list of object and for each objects I would like to get the elements in the array field with key property equal to 1, if any. I have tried array | selectattr("key", "equalto", 1) | first but it fails when there is no element satisfying the condition. I have tried using an if statement with defined condition, but it fails at the first and I can't avoid it since selectattr is a generator.
There is always the possibility to create my own first filter, but I am looking for a pure jinja2 solution.
{
"objects": [
{
"array": [
{
"key": 1
"value": 234
},
{
"key": 2
"value": 235
}
]
},
{
"array": [
{
"key": 3
"value": 256
},
{
"key": 2
"value": 231
}
]
}
]
}

How can I filter for entries that do NOT contain a key-value pair within a nested array

Let's say I have the following JSON output:
{
"Stacks": [
{
"StackName": "hello-world",
"Tags": [
{
"Key": "environment",
"Value": "sandbox"
},
{
"Key": "Joe Shmo",
"Value": "Dev"
}
]
},
{
"StackName": "hello-man",
"Tags": [
{
"Key": "environment",
"Value": "live"
},
{
"Key": "Tandy",
"Value": "Dev"
}
]
}
]
}
How would I write a jq query to grab all StackNames for stacks that do NOT have a Tags value "Key": "Joe Shmo"? So the result would return simply hello-man.
.Stacks[]
| select( any(.Tags[]; .Key == "Joe Shmo" ) | not)
| .StackName
This checks for equality efficiently (any has short-circuit semantics), whereas contains would check for containment.
Using contains, like this:
jq -r '.Stacks[]|select(.Tags|contains([{"Key": "Joe Shmo"}])|not).StackName'
Note: -r removes the quotes from output, otherwise jq would print "hello-man" (within double quotes)

Modifying array of key value in JSON jq

In case, I have an original json look like the following:
{
"taskDefinition": {
"containerDefinitions": [
{
"name": "web",
"image": "my-image",
"environment": [
{
"name": "DB_HOST",
"value": "localhost"
},
{
"name": "DB_USERNAME",
"value": "user"
}
]
}
]
}
}
And I would like to inplace modify the value for the matched key like so:
jq '.taskDefinition.containerDefinitions[0].environment[] | select(.name=="DB_USERNAME") | .value="new"' json
I got the output
{
"name": "DB_USERNAME",
"value": "new"
}
But I want more like in-place modify or the whole json from the original with new value modified, like this:
{
"taskDefinition": {
"containerDefinitions": [
{
"name": "web",
"image": "my-image",
"environment": [
{
"name": "DB_HOST",
"value": "localhost"
},
{
"name": "DB_USERNAME",
"value": "new"
}
]
}
]
}
}
Is it possible to do with jq or any known workaround?
Thank you.
Updated
For anyone looking for editing multi-values,
here is the approach I use
JQ=""
for e in DB_HOST=rds DB_USERNAME=xxx; do
k=${e%=*}
v=${e##*=}
JQ+="(.taskDefinition.containerDefinitions[0].environment[] | select(.name==\"$k\") | .value) |= \"$v\" | "
done
jq '${JQ%??}' json
I think there should be more concise way, but this seems working fine.
It is enough to assign to the path, if you are using |=, e.g.
jq '
(.taskDefinition.containerDefinitions[0].environment[] |
select(.name=="DB_USERNAME") | .value) |= "new"
' infile.json
Output:
{
"taskDefinition": {
"containerDefinitions": [
{
"name": "web",
"image": "my-image",
"environment": [
{
"name": "DB_HOST",
"value": "localhost"
},
{
"name": "DB_USERNAME",
"value": "new"
}
]
}
]
}
}
Here is a select-free solution using |=:
.taskDefinition.containerDefinitions[0].environment |=
map(if .name=="DB_USERNAME" then .value = "new"
else . end)
Avoiding select within the expression on the LHS of |= makes the solution more robust w.r.t. the version of jq being used.
You might like to consider this alternative to using |=:
walk( if type=="object" and .name=="DB_USERNAME"
then .value="new" else . end)

jq - nested dictionary parser and extract key

I am trying to parse with jq the following structure:
{
"a": {
"sensitive": false,
"type": "string",
"value": "mykeypair"
},
"b": {
"sensitive": false,
"type": "string",
"value": "123"
}
}
and get this as an output:
{
"a": "mykeypair",
"b": "123"
}
I would like the key and as a value, the value of the field 'value'.
Any idea?
Cheers,
If you're merely getting the value of every value in the root object, you could use map_values/1 to get those values.
map_values(.value)
I think this is what you are looking for:
[ to_entries[] | .value = .value.value ] | from_entries
A simpler way:
with_entries(.value |= .value)
Check the result here:
https://jqplay.org/s/uHqfdPoF3e