How to Format a JSON String as a Table Using jq

jqjson

I need to convert a JSON string using jq, as shown below, into a table format for displaying the output in the terminal.

{
  "results": [
    [
      {
        "field": "@timestamp",
        "value": "2023-07-03 08:28:00.000"
      },
      {
        "field": "CpuReserved",
        "value": "8192.0"
      },
      {
        "field": "CpuUtilized",
        "value": "4056.412942708333"
      },
      {
        "field": "MemoryReserved",
        "value": "61440"
      },
      {
        "field": "MemoryUtilized",
        "value": "4311"
      },
      {
        "field": "@ptr",
        "value": "CpABClUKUTE0NjcxNzAzNzI0NzovYXdzL2Vjcy9jb250YWluZXJpbnNpZ2h0cy9pcmwtaW5mcmEtc2hhcmVkLWplbmtpbnMtbWFpbi9wZXJmb3JtYW5jZRABEjUaGAIGSaxosQAAAAAd+TFeAAZKKHIwAAABEiABKIDF9taRMTDA7v3WkTE4BkC9NEjNPFCBIRgAEAAYAQ=="
      }
    ],
    [
      {
        "field": "@timestamp",
        "value": "2023-07-03 08:28:00.000"
      },
      {
        "field": "CpuReserved",
        "value": "8192.0"
      },
      {
        "field": "CpuUtilized",
        "value": "4056.412942708333"
      },
      {
        "field": "MemoryReserved",
        "value": "61440"
      },
      {
        "field": "MemoryUtilized",
        "value": "4311"
      },
      {
        "field": "@ptr",
        "value": "CpABClUKUTE0NjcxNzAzNzI0NzovYXdzL2Vjcy9jb250YWluZXJpbnNpZ2h0cy9pcmwtaW5mcmEtc2hhcmVkLWplbmtpbnMtbWFpbi9wZXJmb3JtYW5jZRABEjUaGAIGSaxosQAAAAAd+TFeAAZKKHIwAAABEiABKIDF9taRMTDA7v3WkTE4BkC9NEjNPFCBIRgAEAEYAQ=="
      }
    ]
  ]
}

What I want to display in the terminal is as follows:

@timestamp                CpuReserved  CpuUtilized         MemoryReserved   MemoryUtilized  
==========================================================================================
2023-07-03 08:16:00.000   8192.0       410.5300065104166   61440            1417
2023-07-03 08:15:00.000   8192.0       702.310791015625    61440            792

Can someone guide me in the right direction?

Best Answer

Maybe:

$ jq -c '.results[]|map(.key=.field)|from_entries|del(."@ptr")' file.json |
   mlr --ijson --opprint --barred cat
+-------------------------+-------------+-------------------+----------------+----------------+
| @timestamp              | CpuReserved | CpuUtilized       | MemoryReserved | MemoryUtilized |
+-------------------------+-------------+-------------------+----------------+----------------+
| 2023-07-03 08:28:00.000 | 8192.0      | 4056.412942708333 | 61440          | 4311           |
| 2023-07-03 08:28:00.000 | 8192.0      | 4056.412942708333 | 61440          | 4311           |
+-------------------------+-------------+-------------------+----------------+----------------+

Without --barred, that looks like:

@̲t̲i̲m̲e̲s̲t̲a̲m̲p̲              C̲p̲u̲R̲e̲s̲e̲r̲v̲e̲d̲ C̲p̲u̲U̲t̲i̲l̲i̲z̲e̲d̲       M̲e̲m̲o̲r̲y̲R̲e̲s̲e̲r̲v̲e̲d̲ M̲e̲m̲o̲r̲y̲U̲t̲i̲l̲i̲z̲e̲d̲
2023-07-03 08:28:00.000 8192.0      4056.412942708333 61440          4311
2023-07-03 08:28:00.000 8192.0      4056.412942708333 61440          4311

You could also pipe the output of that jq command to vd -f json (VisiData) instead of mlr (Miller) to get an interactive table viewer.

Those use jq to extract the information and mlr only to format the table. There is some overlap between the feature set of jq and that of mlr. For instance, you could also remove the @ptr column with mlr's cut:

jq -c '.results[]|map(.key=.field)|from_entries' file.json |
  mlr --ijson --opprint cut -xf @ptr

That jq command, broken down and commented:

jq -c '
  .results[] | # iterate over the elements of the .results array 
               # (which are also arrays)

  map(.key=.field) | # for each of those arrays, transform the
                     # elements (which are objects) by adding a 
                     # field of key "key" with same value as that
                     # with "field" key in each, as that's what
                     # from_entries needs

  from_entries | # transforms those [{"key":"foo","value":"bar"}]
                 # (the "field" field is ignored) to {"foo":"bar"}

  del(."@ptr") # deletes the field with key "@ptr" from those
               # objects' file.json

The result is not JSON, but several JSONs concatenated together, but both jq and mlr support that. With -c (compact), that's NDJSON (newline-delimited JSON) where we have one JSON per line, also supported by vd. To get proper JSON, we'd need:

jq -c '.results|map(map(.key=.field)|from_entries|del(."@ptr"))' file.json

Where we use map on the .results array so it results in another JSON array instead of iterating over the elements. So the end result is one large arrays. That's also supported by jq (obviously as that's proper JSON), mlr and vd, is a bit longer to type and means those tools need to read up to the closing ] at the very end before they have anything to chew on. In practice, I've not checked whether that made any difference in terms of performance though.

Related Question