Photo by Tara-mae Miller / Unsplash

Exercise: jq for testers

Exercises Oct 15, 2025

A set of testing-relevant exercises to help testers use, and see the use of, jq – a tool to filter and manipulate json.

Notes on jq

(maybe on a page)

JSON is a text-based record format as a hierarchy – it's not rows and columns. A JSON record is typically an object {} or a list [], and everything inside is a key: value – and those values can themselves be objects {} (named parts in any order) or lists [] (ordered rows), giving the hierarchy. The basic unit is key : value , one per line.

jq lets you filter based on name (.foo shows you a top level value thta has the key foo), lets you drill down in to a hierarchy (.foo.bar shows you the value for the property bar under foo), and lets you iterate or access specifics in a list with [n] (.[].foo will, if you've given jq a list, give you the value of the property foo on every item on the list, while .[0].foo will give you the first.

jq stacks commands – you'll send information from left to right with |, and you'll often send to one of jq's collection commands e.g. .[].foo | unique | sort, or to a selection command .foo[] | select(.bar == "moon")

Ultimately, it's a powerful filtering language for extracting, exploring and creating data. I'll not be rewriting the manual, and the cookbook shows the complex tasks the tool can manage.


Reading a query:

  • if you see a jq with information in all the [], it’s like dot notation – it’s effectively an address.
  • If a [] is empty, it’s iterating, and if you see a | it’s filtering or aggregating, sometimes to another filter, sometimes to a keyword like sort or select( .
  • If you see a .. it’s traversing the whole tree, and a ? means it’s finding stuff, or checking stuff and handling errors.
  • If you see . then that’s picking out everything, and .foo picks out the thing called foo at whatever level the jq reader is at. , separates and aggregates queries.
  • If there’s a [ and ] around something that produces information, then that information is being wrapped up as an array. If it’s wrapped in { and } you’re getting an object.

For the exercises, we'll use online jq tools. As with regex, I reckon that we don't need to be expert with jq, if we have tools to help build and explore queries. All the following tools take in json and a query, and show you the results in real time.

  • JQ / Playground is a page that lets you ask for jq queries in plain language, and to see their effects in real time.
  • JQ Play Offline is a page that shows you the results of queries more readably, and lets you work with command-line options
  • The JQ Playground lets you get your json from an http endpoint – and is from the makers of jq. It runs slightly less swiftly.

jq is primarily a command line tool: These pages are handy for building or testing a query, and for learning through playing, but you'll want the command line version to do work. The tools abstract away the command-line syntax, which goes jq -c '.fruit.color,.fruit.price' fruit.json – wrapping the command in '', acting on a file, and giving an option.

However, jq is not always available, because it's not one of the core xNIX toolset. Its official page is jqlang.org, and

Exercise 1 – exploring data with jq

10 minutes

We'll use the data below, which is a generated JSON output from an imaginary system that logs events to an events object, and logs traces to a traces object.

  • use keys to show the top-level keys
  • use traces[] to show everything in the traces array
  • use .traces[].traceId to show the traceIDs.
    • Aside: Compare with .traces[] .traceId and .traces[] | traceId
    • Aside: Compare with .traces | map(.traceId) | unique and
      [ .traces[] | .traceId ] | unique
  • use events[] to show everything in the events array
  • use events[0] to show the first event in the events array
  • use .events | map(.traceId) | unique to show all the unique traceIDs in the events array.
  • use .traces | map({(.traceId): [.spans[].name]}) | add to show all event names for each trace.
  • use .events[] | select(.type == "mobile_request") | .data.device to see all devices for mobile requests.

This will, I hope, let you be familiar with accessing by name, with iterating and picking – and familiar with the data itself.

Exercise 2 – building queries

10 mins

Imagine something you'd like to know as a tester from this data. Use an LLM of your choice, or JQ / Playground above, to find a query. Try it out. Parse the query you got.

Share with the rest of the group – they may try it out, parse it themselves, check that the data is right, or see if they can get the same data in a different way.

Exercise 3 – Generating data with jq

Wrap a jq command in [] or {} to generate an array or object.

Sample Data

{
  "traces": [
    {
      "traceId": "123456789",
      "duration": 2000,
      "spans": [
        {
          "id": "span1",
          "name": "web_request",
          "startTime": 1634491200000,
          "endTime": 1634491201000,
          "tags": {
            "http.method": "GET",
            "http.url": "/api/users"
          }
        },
        {
          "id": "span2",
          "name": "database_query",
          "startTime": 1634491201000,
          "endTime": 1634491201500,
          "tags": {
            "db.query": "SELECT * FROM users WHERE id = 1"
          }
        },
        {
          "id": "span3",
          "name": "cache_lookup",
          "startTime": 1634491201500,
          "endTime": 1634491201800,
          "tags": {
            "cache.key": "user_profile_1"
          }
        }
      ]
    },
    {
      "traceId": "987654321",
      "duration": 3000,
      "spans": [
        {
          "id": "span1",
          "name": "web_request",
          "startTime": 1634491202000,
          "endTime": 1634491203000,
          "tags": {
            "http.method": "POST",
            "http.url": "/api/payments"
          }
        },
        {
          "id": "span2",
          "name": "payment_processing",
          "startTime": 1634491203000,
          "endTime": 1634491204000,
          "tags": {
            "payment.amount": 100.00,
            "payment.method": "credit_card"
          }
        },
        {
          "id": "span3",
          "name": "fraud_check",
          "startTime": 1634491204000,
          "endTime": 1634491204500,
          "tags": {
            "fraud.score": 10
          }
        }
      ]
    },
    {
      "traceId": "456789123",
      "duration": 1500,
      "spans": [
        {
          "id": "span1",
          "name": "mobile_request",
          "startTime": 1634491205000,
          "endTime": 1634491205500,
          "tags": {
            "http.method": "GET",
            "http.url": "/api/products"
          }
        },
        {
          "id": "span2",
          "name": "product_catalog_lookup",
          "startTime": 1634491205500,
          "endTime": 1634491206000,
          "tags": {
            "product.category": "electronics"
          }
        }
      ]
    },
    {
      "traceId": "321654987",
      "duration": 2500,
      "spans": [
        {
          "id": "span1",
          "name": "web_request",
          "startTime": 1634491207000,
          "endTime": 1634491208000,
          "tags": {
            "http.method": "POST",
            "http.url": "/api/orders"
          }
        },
        {
          "id": "span2",
          "name": "inventory_check",
          "startTime": 1634491208000,
          "endTime": 1634491208500,
          "tags": {
            "product.id": "prod123",
            "quantity": 5
          }
        },
        {
          "id": "span3",
          "name": "order_processing",
          "startTime": 1634491208500,
          "endTime": 1634491209500,
          "tags": {
            "order.id": "order456",
            "order.total": 50.00
          }
        }
      ]
    },
    {
      "traceId": "159753",
      "duration": 1000,
      "spans": [
        {
          "id": "span1",
          "name": "mobile_request",
          "startTime": 1634491210000,
          "endTime": 1634491210500,
          "tags": {
            "http.method": "GET",
            "http.url": "/api/notifications"
          }
        },
        {
          "id": "span2",
          "name": "notification_fetch",
          "startTime": 1634491210500,
          "endTime": 1634491211000,
          "tags": {
            "notification.type": "promotion",
            "notification.message": "25% off sale!"
          }
        }
      ]
    },
    {
      "traceId": "753159",
      "duration": 1800,
      "spans": [
        {
          "id": "span1",
          "name": "web_request",
          "startTime": 1634491212000,
          "endTime": 1634491212500,
          "tags": {
            "http.method": "GET",
            "http.url": "/api/profile"
          }
        },
        {
          "id": "span2",
          "name": "user_profile_fetch",
          "startTime": 1634491212500,
          "endTime": 1634491213000,
          "tags": {
            "user.id": "user123",
            "user.email": "user@example.com"
          }
        },
        {
          "id": "span3",
          "name": "settings_fetch",
          "startTime": 1634491213000,
          "endTime": 1634491213800,
          "tags": {
            "settings.theme": "dark",
            "settings.language": "en"
          }
        }
      ]
    },
    {
      "traceId": "951357",
      "duration": 2200,
      "spans": [
        {
          "id": "span1",
          "name": "mobile_request",
          "startTime": 1634491215000,
          "endTime": 1634491215500,
          "tags": {
            "http.method": "POST",
            "http.url": "/api/feedback"
          }
        },
        {
          "id": "span2",
          "name": "feedback_submit",
          "startTime": 1634491215500,
          "endTime": 1634491216000,
          "tags": {
            "feedback.rating": 4,
            "feedback.comment": "Great app, but could use more features."
          }
        },
        {
          "id": "span3",
          "name": "support_ticket_create",
          "startTime": 1634491216000,
          "endTime": 1634491217200,
          "tags": {
            "ticket.id": "ticket123",
            "ticket.priority": "medium"
          }
        }
      ]
    },
    {
      "traceId": "357951",
      "duration": 1700,
      "spans": [
        {
          "id": "span1",
          "name": "web_request",
          "startTime": 1634491218000,
          "endTime": 1634491218500,
          "tags": {
            "http.method": "GET",
            "http.url": "/api/analytics"
          }
        },
        {
          "id": "span2",
          "name": "data_aggregation",
          "startTime": 1634491218500,
          "endTime": 1634491219000,
          "tags": {
            "data.source": "user_activity",
            "data.timeframe": "last_7_days"
          }
        },
        {
          "id": "span3",
          "name": "report_generation",
          "startTime": 1634491219000,
          "endTime": 1634491219700,
          "tags": {
            "report.id": "weekly_analytics",
            "report.format": "pdf"
          }
        }
      ]
    },
    {
      "traceId": "753951",
      "duration": 2800,
      "spans": [
        {
          "id": "span1",
          "name": "mobile_request",
          "startTime": 1634491220000,
          "endTime": 1634491220500,
          "tags": {
            "http.method": "PUT",
            "http.url": "/api/settings"
          }
        },
        {
          "id": "span2",
          "name": "user_settings_update",
          "startTime": 1634491220500,
          "endTime": 1634491221000,
          "tags": {
            "setting.language": "es",
            "setting.notification_preferences": "email"
          }
        },
        {
          "id": "span3",
          "name": "email_notification",
          "startTime": 1634491221000,
          "endTime": 1634491222800,
          "tags": {
            "email.recipient": "user@example.com",
            "email.subject": "Your settings have been updated"
          }
        }
      ]
    }
  ],
  "events": [
    {
      "timestamp": 1634491200000,
      "type": "user_login",
      "data": {
        "userId": "user123",
        "ipAddress": "192.168.1.100"
      },
      "traceId": "123456789"
    },
    {
      "timestamp": 1634491201000,
      "type": "database_query",
      "data": {
        "query": "SELECT * FROM users WHERE id = 1"
      },
      "traceId": "123456789"
    },
    {
      "timestamp": 1634491201800,
      "type": "cache_hit",
      "data": {
        "cacheKey": "user_profile_1"
      },
      "traceId": "123456789"
    },
    {
      "timestamp": 1634491202000,
      "type": "payment_initiated",
      "data": {
        "paymentId": "payment123",
        "amount": 100.00
      },
      "traceId": "987654321"
    },
    {
      "timestamp": 1634491204000,
      "type": "fraud_check_complete",
      "data": {
        "fraudScore": 10
      },
      "traceId": "987654321"
    },
    {
      "timestamp": 1634491205000,
      "type": "mobile_request",
      "data": {
        "userId": "user456",
        "device": "iPhone"
      },
      "traceId": "456789123"
    },
    {
      "timestamp": 1634491205500,
      "type": "product_catalog_lookup",
      "data": {
        "productCategory": "electronics"
      },
      "traceId": "456789123"
    },
    {
      "timestamp": 1634491207000,
      "type": "order_placed",
      "data": {
        "orderId": "order456",
        "totalAmount": 50.00
      },
      "traceId": "321654987"
    },
    {
      "timestamp": 1634491208500,
      "type": "inventory_check",
      "data": {
        "productId": "prod123",
        "quantity": 5
      },
      "traceId": "321654987"
    },
    {
      "timestamp": 1634491210000,
      "type": "mobile_request",
      "data": {
        "userId": "user789",
        "device": "Android"
      },
      "traceId": "159753"
    },
    {
      "timestamp": 1634491210500,
      "type": "notification_fetched",
      "data": {
        "notificationType": "promotion",
        "notificationMessage": "25% off sale!"
      },
      "traceId": "159753"
    },
    {
      "timestamp": 1634491212000,
      "type": "user_profile_viewed",
      "data": {
        "userId": "user123",
        "userEmail": "user@example.com"
      },
      "traceId": "753159"
    },
    {
      "timestamp": 1634491213000,
      "type": "user_settings_fetched",
      "data": {
        "theme": "dark",
        "language": "en"
      },
      "traceId": "753159"
    },
    {
      "timestamp": 1634491215000,
      "type": "mobile_request",
      "data": {
        "userId": "user456",
        "device": "Android"
      },
      "traceId": "951357"
    },
    {
      "timestamp": 1634491215500,
      "type": "feedback_submitted",
      "data": {
        "rating": 4,
        "comment": "Great app, but could use more features."
      },
      "traceId": "951357"
    },
    {
      "timestamp": 1634491216000,
      "type": "support_ticket_created",
      "data": {
        "ticketId": "ticket123",
        "priority": "medium"
      },
      "traceId": "951357"
    },
    {
      "timestamp": 1634491218000,
      "type": "web_request",
      "data": {
        "userId": "user789",
        "ipAddress": "192.168.1.101"
      },
      "traceId": "357951"
    },
    {
      "timestamp": 1634491218500,
      "type": "data_aggregation",
      "data": {
        "dataSource": "user_activity",
        "timeframe": "last_7_days"
      },
      "traceId": "357951"
    },
    {
      "timestamp": 1634491219000,
      "type": "report_generated",
      "data": {
        "reportId": "weekly_analytics",
        "reportFormat": "pdf"
      },
      "traceId": "357951"
    },
    {
      "timestamp": 1634491220000,
      "type": "mobile_request",
      "data": {
        "userId": "user123",
        "device": "Android"
      },
      "traceId": "753951"
    },
    {
      "timestamp": 1634491220500,
      "type": "user_settings_updated",
      "data": {
        "language": "es",
        "notificationPreferences": "email"
      },
      "traceId": "753951"
    },
    {
      "timestamp": 1634491221000,
      "type": "email_notification_sent",
      "data": {
        "recipient": "user@example.com",
        "subject": "Your settings have been updated"
      },
      "traceId": "753951"
    }
  ]
}

Tags

James Lyndsay

Getting better at software testing. Singing in Bulgarian. Staying in. Going out. Listening. Talking. Writing. Making.