jsonjqi3

Recursive search for array with matching value


I am trying to use jq to find the layout of the currently active container in i3. i3-msg -t get_tree returns a recursive structure of containers with .layout and .focused properties. The trick is that I don't want the .layout of the container with .focused == true, but the one of its parent node:

// partial output, this might be several `.nodes[]` down
{
  "focused": false,
  "layout": "splitv",  // Return this
  "nodes": [
    {
      "focused": true,  // Find by this
      "layout": "splith"  // Don't return this
      "nodes": []
    }
  ]
}

I started off by recursive search from Recursive search values by key:

$ i3-msg -t get_tree | jq -r '.. | select(.focused?) | .layout'
splith

but that returns the wrong value as shown in example before. If jq had a hypothetical parent() function, I would do something like .. | select(.focused?) | parent().parent().layout to get from the current level to .nodes[] and then once more to the parent container.

I then tried to follow How do I print a parent value of an object when I am already deep into the object's children?, but I am running into some errors that I believe are caused by .. operator iterating over non-dict objects:

$ i3-msg -t get_tree | jq -r '.. | select(.nodes[] | select(.focused?)) | .layout'
jq: error (at <stdin>:1): Cannot index number with string "nodes"
$ i3-msg -t get_tree | jq -r '.. | select(.nodes[]? | select(.focused?)) | .layout'
jq: error (at <stdin>:1): Cannot index number with string "nodes"
$ i3-msg -t get_tree | jq -r '.. | select(.nodes?[] | select(.focused?)) | .layout'
jq: error (at <stdin>:1): Cannot iterate over null (null)
$ i3-msg -t get_tree | jq -r '.. | select(.nodes? | select(.focused?)) | .layout'
# no output

Here is a gist with my dump of i3-msg -t get_tree for anyone who would like to have a go without installing i3.


Solution

  • Instead of selecting an item from whose context .focused might evaluate to true, select an item under which .nodes might be an array, and in any (i.e. at least one) of which's children's contexts .focused might evaluate to true:

    .. | select(.nodes | any(.focused))?.layout
    

    Demo

    To avoid the "might" circumstances along with the error suppression operator ?, check for arrays and objects explicitly. Under the latter, any key is either defined or evaluates to (a "falsey") null if missing:

    .. | objects | select(.nodes | arrays | any(objects.focused)).layout
    

    Demo

    To get the whole parent, just remove the final .layout.