skip to Main Content

I’m trying to use jq to parse the output of some tool. Specifically I’m looking for a count of various HTTP status codes in the output json. I.e. something like

jq -c '. | <filter>' test.json
{200: 1, 301: 1, 403: 1}

from the following input json

#test.json
[
    {
        "content-length": 45,
        "path": "/foo",
        "redirect": null,
        "status": 200
    },
    {
        "content-length": 40,
        "path": "/bar",
        "redirect": null,
        "status": 301
    },
    {
        "content-length": 1150,
        "path": "/baz",
        "redirect": null,
        "status": 403
    }
]

I could just loop through in bash with something like

$ for i in 200 301 403; do echo -n $i "    "; jq '[.[] | select(.status | tostring =="'$i'")] | length' test.json ; done
200     1
301     1
403     1

but that seems inefficient. Trying to loop in jq feels like the better way to go, but the actual syntax to do it is a bit beyond me. I haven’t had any luck finding exapmples, and I haven’t had any luck trying to interpret the man pages

$ jq '[200, 301, 403] as $s | {$s: [.[] | select(.status == $s)] | length}' test.json 
jq: error: syntax error, unexpected ':', expecting '}' (Unix shell quoting issues?) at <top-level>, line 1:
[200, 301, 403] as $s | {$s: [.[] | select(.status == $s)] | length}                           
jq: error: May need parentheses around object key expression at <top-level>, line 1:
[200, 301, 403] as $s | {$s: [.[] | select(.status == $s)] | length}                         
jq: 2 compile errors

The python equivalent to what I want to do, in case that’s clearer, is

import json
from collections import Counter
dat = json.load(open("test.json"))
print(Counter(d["status"] for d in dat))'
# Counter({200: 1, 301: 1, 403: 1})

3

Answers


  1. Using the approach suggested by this solution:

    $ jq 'def counter(stream):
    reduce stream as $s ({}; .[$s|tostring] += 1);
    counter(.[].status)' test.json
    {
      "200": 1,
      "301": 1,
      "403": 1
    }
    
    Login or Signup to reply.
  2. This works:

    reduce .[] as $row ({}; .[$row.status | tostring] += 1)
    

    The trick is realizing that you never really wanted select in the first place, that’s a relic of the "bash is looping and deciding what status I’m looking for" way of working. tostring is because jq objects can’t have numbers as keys, only strings.

    Login or Signup to reply.
  3. Alternatively, you could just group the responses and check the length of the groupings. This approach works well if you’re just dumping the counts and not formatting as json.

    $ jq -r 'group_by(.status)[] | "(.[0].status)t(length)"' test.json
    

    But you can still format as json if you wanted, there’s plenty of ways to skin this cat.

    $ jq '[group_by(.status)[] | {key:"(.[0].status)", value:length}] | from_entries' test.json
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search