Search code examples
jsonjq

jq: how do I handle recursion in this case?


I have JSON output as shown below (the output of lsblk, greatly simplified). I need to generate CSV output which shows the name of a disk, together with a list of mount points, if any. I can generate this for simple cases, but the problem is that there's a recursive children list, containing additional mountpoints, which I don't know how to handle.

There are a couple of other SO questions which ask specifically about parsing lsblk output, but they show a simpler (old?) lsblk output, which is flat (and with mountpoints which aren't arrays).

For the example below, I'd like to get CSV output that looks like (without the comments, obviously):

"sda"                       # no mountpoints
"sdb","foo1","foo2","foo3"  # no top-level mountpoint, 3 [grand]children

Note:

  1. All mountpoints are arrays. Presumably they can contain more than one element (or no elements), but I've never seen this
  2. Any mountpoints can contain null entries, but it seems like the array is always present
  3. The blockdevices object contains a list, but only entries of type disk are relevant

Any help much appreciated - I've spent several hours trying to find a walk/recurse solution which handles the children.

{
   "blockdevices": [
      {
         "type": "disk",
         "name": "sda",
         "etc" : "ignore",
         "mountpoints": [ null ]
      },{
         "type": "loop",
         "etc" : "ignore everything"
      },{
         "type": "disk",
         "name": "sdb",
         "etc" : "ignore",
         "mountpoints": [ null ],
         "children": [
            {
               "name": "ignore",
               "etc" : "ignore",
               "mountpoints": [ "foo1" ]
            },{
               "name": "ignore",
               "etc" : "ignore",
               "mountpoints": [ null ],
               "children": [
                  {
                     "name": "ignore",
                     "etc" : "ignore",
                     "mountpoints": [ "foo2" ]
                  },{
                     "name": "ignore",
                     "etc" : "ignore",
                     "mountpoints": [ "foo3" ]
                  }
               ]
            }
         ]
      }
   ]
}

Solution

  • Probably not the cleanest way, but we could:

    1. Filter on select(.type == "disk") to prevent empty starting arrays.
    2. Recursive (..) get all .mountpoints
      (or empty array as fallback (// []))
    3. flatten the arrays to get a single output.
    4. Then remove all null from the arrays (values).

    .blockdevices[] | select(.type == "disk") | [ 
        .name, 
        (.. | (.mountpoints? // []) | flatten[] | values) 
    ]
    

    output:

    [
      "sda"
    ]
    [
      "sdb",
      "foo1",
      "foo2",
      "foo3"
    ]
    

    JqPlay Demo