1

I want to extract the subtree containing the focused window from i3-msg -t get_tree with jq. I know the focused window can be found with

i3-msg -t get_tree | jq ".. | (.nodes? // empty)[] | select(.focused == true)"

A simple example would be:

{
  "node": [
    {
      "node": {
        "foo": "bar"
      }
    },
    {
      "node": {
        "foo": "foo"
      }
    }
  ]
}

And the output should if searching for a node containg .foo == "bar" should return

{
  "node": [
    {
      "node": {
        "foo": "bar"
      }
    }
  ]
}

But I can't seem to find a proper method to extract the subtree spanning from the root to this node.

TylerH
  • 20,799
  • 66
  • 75
  • 101

2 Answers2

2
.node |= map(select(.node.foo == "bar"))

This concept is referred to as Update assignment

peak
  • 105,803
  • 17
  • 152
  • 177
vintnes
  • 2,014
  • 7
  • 16
  • After some digging I found that this solution yielded the best results when filtering out the elements that had non-empty nodes `i3-msg -t get_tree | jq ".. | (.nodes? // empty)[] | .nodes |= map(select(.nodes[].focused == true)) | select(.nodes != [])"` – Jakob Guldberg Aaes Apr 08 '20 at 09:43
  • @vintnes - The term "complex assignment" refers to assignments in which the LHS is a complex term. Here the LHS is a simple term. – peak Apr 09 '20 at 18:55
  • @peak The documentation varies heavily across versions; you can call it whatever you want. I reverted your edit because you broke my link. – vintnes Apr 09 '20 at 20:15
  • @vintnes - Sorry if I broke a link. In v1.3, v1.4, v1.5 and v1.6 of the documentation, the section on "Assignment" includes the subsection on `|=`. Similarly for the "development" version (https://stedolan.github.io/jq/manual/#Assignment) – peak Apr 09 '20 at 20:22
2

The original question has two distinct sub-questions, one related to the use of .. without reference to a posted JSON sample, and the other based on a specific type of JSON input. This response uses a strategy based on using paths along the lines of:

reduce pv as [$p,$v] (null; setpath($p; $v))

This may or may not handle arrays as desired, depending in part on what is desired. If null values in arrays are not wanted in general, then adding a call to walk/1 as follows would be appropriate:

walk(if type == "array" then map(select(. != null)) else . end)

Alternatively, if the null values that are present in the original must be preserved, the strategy detailed in the Appendix below may be used.

(1) Problem characterized by using ..

def pv:
 paths as $p
 | getpath($p)
 | . as $v
 | (.nodes? // empty)[] | select(.focused == true)
 | [$p,$v];

reduce pv as [$p,$v] (null; setpath($p; $v))

As mentioned above, to eliminate all the nulls in all arrays, you could tack on a call to walk/1. Otherwise, if the null values inserted in arrays by setpath to preserve aspects of the original structure, see the Appendix below.

(2) For the sample JSON, the following suffices:

def pv:
 paths as $p
 | getpath($p)
 | . as $v
 | (.node? // empty) | select(.foo == "bar")
 | [$p,$v];

reduce pv as [$p,$v] (null; setpath($p; $v))

For the given sample, this produces:

{"node":[{"node":{"foo":"bar"}}]}

For similar inputs, if one wants to eliminate null values from arrays, simply tack on the call to walk/1 as before; see also the Appendix below.

Appendix

If the null values potentially inserted into arrays by setpath to preserve the original structure are not wanted, the simplest would be to change null values in the original JSON to some distinctive value (e.g. ":null:"), perform the selection, trim the null values, and then convert the distinctive value back to null.

Example

For example, consider this variant of the foo/bar example:

{
  "node": [
    {
      "node": {
        "foo": "foo0"
      }
    },
    {
      "node": {
        "foo": "bar",
        "trouble": [
          null,
          1,
          null
        ]
      }
    },
    {
      "node": {
        "foo": "foo1"
      }
    },
    {
      "node": {
        "foo": "bar",
        "trouble": [
          1,
          2,
          3
        ]
      }
    }
  ],
  "nodes": [
    {
      "node": {
        "foo": "foo0"
      }
    },
    {
      "node": {
        "foo": "bar",
        "trouble": [
          null,
          1,
          null
        ]
      }
    }
  ]
}

Using ":null:" as the distinctive value, the following variant of the "main" program previously shown for this case may be used:

walk(if type == "array" then map(if . == null then ":null:" else . end) else . end)
| reduce pv as [$p,$v] (null; setpath($p; $v))
| walk(if type == "array"
       then map(select(. != null) | if . == ":null:" then null else . end)
       else . end)
peak
  • 105,803
  • 17
  • 152
  • 177
  • `walk/1` is very powerful. One question: suppose the selected subtree has `null` array elements that shouldn't be removed. Do you know an easy way to limit it to the "upper" part of the result where all the `null` array elements are consequences of `setpath`? – jq170727 Apr 09 '20 at 07:35
  • Looking at [`walk/1`](https://github.com/stedolan/jq/blob/master/src/builtin.jq#L255) I suspect computing the max depth from `getpath` and passing it to a `walk/2` that stopped at a depth limit might work. – jq170727 Apr 09 '20 at 07:43
  • @jq170727 - Please see the update (especially the Appendix). One semantically equivalent alternative would be to use a special version of setpath that employs a distinctive value instead of null, but it would be much more complex. Any other strategy will either fail or be very complex to implement. – peak Apr 09 '20 at 18:49