Guss
Guss

Reputation: 32354

in jq, can a select be optional (i.e. if nothing found - don't filter out the element)

I'm using JQ to filter a list of things, and then for each found thing - reformat it to a single string by extracting values from sub-keys.

The problem is that is one of the sub-keys is missing, the entire line is omitted.

Consider the following example document:

{
  "items": [
    {
      "id": "A",
      "active": true,
      "tags": [
        { "name": "foo", "value": 1 }
      ]
    },
    {
      "id": "B",
      "active": true,
      "tags": [
        { "name": "foo", "value":1 },
        {"name": "bar", "value":2}
      ]
    },
    {
      "id": "C",
      "active": false,
      "tags": [
        { "name": "foo", "value":1 },
        { "name": "baz", "value":3 }
      ]
    }
  ]
}

Now I want to select all active items, and create for each a single line describing the item's ID as well as the value of both its foo and bar tags.

Initially I've done something like this:

jq -r '
   .items[] | 
   select ( .active == true ) | 
   ( .id + " -> [" + 
     ( .tags[] | select( .name == "foo" ) | .value | tostring ) +
     ", " +
     ( .tags[] | select( .name == "bar" ) | .value | tostring ) +
     "]"
   )
'

But because bar is only included in item B, the line for item A gets filtered out.

Is there a way to make the sub-select optional so that if the select fails, I get an empty string or something like that?

Upvotes: 2

Views: 1111

Answers (1)

peak
peak

Reputation: 116870

You could patch your query using if ... then ... else ... end or perhaps //, but it would be better to address some other issues as well, e.g. as follows:

 .items[]
 | select ( .active == true ) 
 | .id + " -> ["
   + (.tags | map(select(.name == "foo") | .value) | join(";"))
   + ", "
   + (.tags | map(select(.name == "bar") | .value) | join(";"))
   + "]"

With jq version 1.5 or earlier, you would need to add a call to tostring, or to use string interpolation, e.g.

   .items[]
   | select ( .active == true ) 
   | .id 
     + " -> [\(.tags | map(select(.name == "foo") | "\(.value)") | join(";") ), "
     +      "\(.tags | map(select(.name == "bar") | "\(.value)" ) | join(";"))"
     + "]"

Variant using first and string interpolation

.items[]
| select ( .active == true )
| (first(.tags[] | select(.name == "foo") | .value) // "")  as $v
| (first(.tags[] | select(.name == "bar") | .value) // "")  as $w
| "\(.id) -> [\($v), \($w)]"

Using string interpolation circumvents the potential need to use tostring.

Upvotes: 2

Related Questions