简体   繁体   中英

Can jq handle nested JSON data?

Maybe this is too much of a stretch for 'jq' and I need to jump into the python json module, but let's throw down a challenge for the gurus!!

I have some JSON data (from the i3 window manager) that looks a bit like this (simplified). I want to dig down through an arbitrary number of levels of .nodes/.floating_nodes and pull out .nodes or .floating_nodes that have "window" set to non-null. :

{
  "floating_nodes": [],
  "nodes": [
    {
      "floating_nodes": [],
      "nodes": [
        {
          "floating_nodes": [],
          "nodes": [
            {
              "floating_nodes": [],
              "nodes": [],
              "window": null,
              "name": "__i3_scratch",
            }
          ],
          "window": null,
          "name": "foobar",
        }
      ],
      "window": null,
      "name": "__i3",
    },
    {
      "floating_nodes": [],
      "nodes": [
        {
          "floating_nodes": [],
          "nodes": [],
          "window": null,
          "name": "topdock",
        },
        {
          "floating_nodes": [],
          "nodes": [
            {
              "floating_nodes": [],
              "nodes": [
                {
                  "floating_nodes": [],
                  "nodes": [],
                  "window": 8388613,
                  "name": "16:11 bhepple:.../~ — Konsole",
                  "rect": {
                    "height": 1061,
                    "width": 1920,
                    "y": 0,
                    "x": 0
                  },
              ],
              "window": null,
              "name": "1",
            }
          ],
          "window": null,
          "name": "content",
        },
        {
          "floating_nodes": [],
          "nodes": [
            {
              "floating_nodes": [],
              "nodes": [],
              "window": 14680070,
              "name": "i3bar for output VNC-0",
            }
          ],
          "window": null,
          "name": "bottomdock",
        }
      ],
      "window": null,
      "name": "VNC-0",
    }
  ],
  "window": null,
  "name": "root",
}

My sample JSON was a bit broken by my simplifications so here's the full version:

{"id":10883136,"type":"root","orientation":"horizontal","scratchpad_state":"none","percent":null,"urgent":false,"focused":false,"layout":"splith","workspace_layout":"default","last_split_layout":"splith","border":"pixel","current_border_width":-1,"rect":{"x":0,"y":0,"width":1920,"height":1080},"deco_rect":{"x":0,"y":0,"width":0,"height":0},"window_rect":{"x":0,"y":0,"width":0,"height":0},"geometry":{"x":0,"y":0,"width":0,"height":0},"name":"root","window":null,"nodes":[{"id":10883568,"type":"output","orientation":"none","scratchpad_state":"none","percent":0.5,"urgent":false,"focused":false,"layout":"output","workspace_layout":"default","last_split_layout":"splith","border":"pixel","current_border_width":-1,"rect":{"x":0,"y":0,"width":1920,"height":1080},"deco_rect":{"x":0,"y":0,"width":0,"height":0},"window_rect":{"x":0,"y":0,"width":0,"height":0},"geometry":{"x":0,"y":0,"width":0,"height":0},"name":"__i3","window":null,"nodes":[{"id":10884128,"type":"con","orientation":"horizontal","scratchpad_state":"none","percent":null,"urgent":false,"focused":false,"layout":"splith","workspace_layout":"default","last_split_layout":"splith","border":"pixel","current_border_width":-1,"rect":{"x":0,"y":0,"width":0,"height":0},"deco_rect":{"x":0,"y":0,"width":0,"height":0},"window_rect":{"x":0,"y":0,"width":0,"height":0},"geometry":{"x":0,"y":0,"width":0,"height":0},"name":"content","window":null,"nodes":[{"id":10884720,"type":"workspace","orientation":"none","scratchpad_state":"none","percent":null,"urgent":false,"focused":false,"layout":"splith","workspace_layout":"default","last_split_layout":"splith","border":"pixel","current_border_width":-1,"rect":{"x":0,"y":0,"width":0,"height":0},"deco_rect":{"x":0,"y":0,"width":0,"height":0},"window_rect":{"x":0,"y":0,"width":0,"height":0},"geometry":{"x":0,"y":0,"width":0,"height":0},"name":"__i3_scratch","num":-1,"window":null,"nodes":[],"floating_nodes":[],"focus":[],"fullscreen_mode":1,"sticky":false,"floating":"auto_off","swallows":[]}],"floating_nodes":[],"focus":[10884720],"fullscreen_mode":0,"sticky":false,"floating":"auto_off","swallows":[]}],"floating_nodes":[],"focus":[10884128],"fullscreen_mode":0,"sticky":false,"floating":"auto_off","swallows":[]},{"id":10886256,"type":"output","orientation":"none","scratchpad_state":"none","percent":0.5,"urgent":false,"focused":false,"layout":"output","workspace_layout":"default","last_split_layout":"splith","border":"pixel","current_border_width":-1,"rect":{"x":0,"y":0,"width":1920,"height":1080},"deco_rect":{"x":0,"y":0,"width":0,"height":0},"window_rect":{"x":0,"y":0,"width":0,"height":0},"geometry":{"x":0,"y":0,"width":0,"height":0},"name":"VNC-0","window":null,"nodes":[{"id":10886944,"type":"dockarea","orientation":"none","scratchpad_state":"none","percent":null,"urgent":false,"focused":false,"layout":"dockarea","workspace_layout":"default","last_split_layout":"splith","border":"pixel","current_border_width":-1,"rect":{"x":0,"y":0,"width":1920,"height":0},"deco_rect":{"x":0,"y":0,"width":0,"height":0},"window_rect":{"x":0,"y":0,"width":0,"height":0},"geometry":{"x":0,"y":0,"width":0,"height":0},"name":"topdock","window":null,"nodes":[],"floating_nodes":[],"focus":[],"fullscreen_mode":0,"sticky":false,"floating":"auto_off","swallows":[{"dock":2,"insert_where":2}]},{"id":10887648,"type":"con","orientation":"horizontal","scratchpad_state":"none","percent":null,"urgent":false,"focused":false,"layout":"splith","workspace_layout":"default","last_split_layout":"splith","border":"pixel","current_border_width":-1,"rect":{"x":0,"y":0,"width":1920,"height":1061},"deco_rect":{"x":0,"y":0,"width":0,"height":0},"window_rect":{"x":0,"y":0,"width":0,"height":0},"geometry":{"x":0,"y":0,"width":0,"height":0},"name":"content","window":null,"nodes":[{"id":10889056,"type":"workspace","orientation":"horizontal","scratchpad_state":"none","percent":null,"urgent":false,"focused":false,"layout":"splith","workspace_layout":"default","last_split_layout":"splith","border":"pixel","current_border_width":-1,"rect":{"x":0,"y":0,"width":1920,"height":1061},"deco_rect":{"x":0,"y":0,"width":0,"height":0},"window_rect":{"x":0,"y":0,"width":0,"height":0},"geometry":{"x":0,"y":0,"width":0,"height":0},"name":"1","num":1,"window":null,"nodes":[{"id":10895664,"type":"con","orientation":"none","scratchpad_state":"none","percent":1.0,"urgent":false,"focused":true,"layout":"splith","workspace_layout":"default","last_split_layout":"splith","border":"pixel","current_border_width":1,"rect":{"x":0,"y":0,"width":1920,"height":1061},"deco_rect":{"x":0,"y":0,"width":0,"height":0},"window_rect":{"x":1,"y":1,"width":1918,"height":1059},"geometry":{"x":0,"y":0,"width":958,"height":1059},"name":"16:11 bhepple:.../~ — Konsole","window":8388613,"window_properties":{"class":"konsole","instance":"konsole","title":"16:11 bhepple:.../~ — Konsole","transient_for":null},"nodes":[],"floating_nodes":[],"focus":[],"fullscreen_mode":0,"sticky":false,"floating":"auto_off","swallows":[]}],"floating_nodes":[],"focus":[10895664],"fullscreen_mode":1,"sticky":false,"floating":"auto_off","swallows":[]}],"floating_nodes":[],"focus":[10889056],"fullscreen_mode":0,"sticky":false,"floating":"auto_off","swallows":[]},{"id":10888352,"type":"dockarea","orientation":"none","scratchpad_state":"none","percent":null,"urgent":false,"focused":false,"layout":"dockarea","workspace_layout":"default","last_split_layout":"splith","border":"pixel","current_border_width":-1,"rect":{"x":0,"y":1061,"width":1920,"height":19},"deco_rect":{"x":0,"y":0,"width":0,"height":0},"window_rect":{"x":0,"y":0,"width":0,"height":0},"geometry":{"x":0,"y":0,"width":0,"height":0},"name":"bottomdock","window":null,"nodes":[{"id":10891392,"type":"con","orientation":"none","scratchpad_state":"none","percent":1.0,"urgent":false,"focused":false,"layout":"splith","workspace_layout":"default","last_split_layout":"splith","border":"pixel","current_border_width":1,"rect":{"x":0,"y":1061,"width":1920,"height":19},"deco_rect":{"x":0,"y":0,"width":0,"height":0},"window_rect":{"x":0,"y":0,"width":1920,"height":19},"geometry":{"x":0,"y":1061,"width":3840,"height":19},"name":"i3bar for output VNC-0","window":14680070,"window_properties":{"class":"i3bar","instance":"i3bar","title":"i3bar for output VNC-0","transient_for":null},"nodes":[],"floating_nodes":[],"focus":[],"fullscreen_mode":0,"sticky":false,"floating":"auto_off","swallows":[]}],"floating_nodes":[],"focus":[10891392],"fullscreen_mode":0,"sticky":false,"floating":"auto_off","swallows":[{"dock":3,"insert_where":2}]}],"floating_nodes":[],"focus":[10887648,10886944,10888352],"fullscreen_mode":0,"sticky":false,"floating":"auto_off","swallows":[]}],"floating_nodes":[],"focus":[10886256,10883568],"fullscreen_mode":0,"sticky":false,"floating":"auto_off","swallows":[]}

Sure, jq can do this!

First, we use the recurse operator, .. , to recursively iterate through everything. Then, we get all the .nodes and .floating_nodes , and we use ? to ignore errors that would arise from trying to get properties from numbers, strings or objects. Then, we get all the elements in these arrays with [] . Again, ignoring the errors in the previous step left some null s, so we ignore the errors from trying to use [] on null s with a ? . Lastly, we pipe everything to select(.window != null) , and we wrap every result in an array for easier treatment.

jq '[.. | .floating_nodes?, .nodes? | .[]? | select(.window != null)]'

如果window属性只能出现在nodefloating_node对象上,则可以通过仅搜索具有window属性的所有对象来简化操作。

.. | objects | select(.window != null)

Working off Santiago's solution and trying to backfill for what may be lacking in jq-1.3 I got this to work:

jq 'recurse(.[]) | .floating_nodes, .nodes | .[] | select(.window != null) | select(.focused == true) | .name'

... it also gave lots of error messages such as:

jq: error: Cannot iterate over number
jq: error: Cannot iterate over null
jq: error: Cannot iterate over null
jq: error: Cannot index number with string

The following has been tested with jq 1.3, 1.4, and 1.5:

recurse(if type == "object" or type == "array" then .[] else empty end)
| select(type == "object")
| ( select(.nodes|type == "array") | .nodes[]),
  ( select(.floating_nodes|type == "array") | .floating_nodes[])
| select( .window != null ) 

Using the "full version" of the given input, jq 1.3 produces the following output:

{
  "swallows": [],
  "floating": "auto_off",
  "sticky": false,
  "fullscreen_mode": 0,
  "focus": [],
  "floating_nodes": [],
  "nodes": [],
  "window_properties": {
    "transient_for": null,
    "title": "16:11 bhepple:.../~ — Konsole",
    "instance": "konsole",
    "class": "konsole"
  },
  "window": 8388613,
  "name": "16:11 bhepple:.../~ — Konsole",
  "layout": "splith",
  "focused": true,
  "urgent": false,
  "percent": 1,
  "scratchpad_state": "none",
  "orientation": "none",
  "type": "con",
  "id": 10895664,
  "workspace_layout": "default",
  "last_split_layout": "splith",
  "border": "pixel",
  "current_border_width": 1,
  "rect": {
    "height": 1061,
    "width": 1920,
    "y": 0,
    "x": 0
  },
  "deco_rect": {
    "height": 0,
    "width": 0,
    "y": 0,
    "x": 0
  },
  "window_rect": {
    "height": 1059,
    "width": 1918,
    "y": 1,
    "x": 1
  },
  "geometry": {
    "height": 1059,
    "width": 958,
    "y": 0,
    "x": 0
  }
}
{
  "swallows": [],
  "floating": "auto_off",
  "sticky": false,
  "fullscreen_mode": 0,
  "focus": [],
  "floating_nodes": [],
  "nodes": [],
  "window_properties": {
    "transient_for": null,
    "title": "i3bar for output VNC-0",
    "instance": "i3bar",
    "class": "i3bar"
  },
  "window": 14680070,
  "name": "i3bar for output VNC-0",
  "layout": "splith",
  "focused": false,
  "urgent": false,
  "percent": 1,
  "scratchpad_state": "none",
  "orientation": "none",
  "type": "con",
  "id": 10891392,
  "workspace_layout": "default",
  "last_split_layout": "splith",
  "border": "pixel",
  "current_border_width": 1,
  "rect": {
    "height": 19,
    "width": 1920,
    "y": 1061,
    "x": 0
  },
  "deco_rect": {
    "height": 0,
    "width": 0,
    "y": 0,
    "x": 0
  },
  "window_rect": {
    "height": 19,
    "width": 1920,
    "y": 0,
    "x": 0
  },
  "geometry": {
    "height": 19,
    "width": 3840,
    "y": 1061,
    "x": 0
  }
}

Here is a solution which uses tostream to scan a stream of [path,value] arrays from the given input. It then uses select to pick out only paths ending in "window" and whose corresponding value is not null. Finally it uses foreach and getpath to produce a stream of the containing objects.

foreach (   tostream
          | select(length == 2 and .[0][-1] == "window" and .[1] != null)
        ) as $p (
    .
  ; .
  ; getpath($p[0][:-1])
)

EDIT: I now realize a filter of the form foreach E as $X (.; .; R) can almost always be rewritten as E as $X | R E as $X | R so the above is really just

  ( tostream | select(length == 2 and .[0][-1] == "window" and .[1] != null) ) as $p
|  getpath($p[0][:-1])

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM