Jakob Guldberg Aaes
Jakob Guldberg Aaes

Reputation: 844

How to get subtree of json containing known object

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.

Upvotes: 1

Views: 1135

Answers (2)

vintnes
vintnes

Reputation: 2030

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

This concept is referred to as Update assignment

Upvotes: 2

peak
peak

Reputation: 116780

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)

Upvotes: 2

Related Questions