kJQ filters

Overview

JQ is a popular, practical language described as 'like sed for JSON data'.

Data inspect supports JQ-like filters on Kafka topics. We call this kJQ.

kJQ is fast, easily scanning tens of thousands of messages from a Kafka topic each second.

Sample KJQ Query

The kJQ input field provides context highlighting, auto-completion, command memory (press up-arrow to view previous filters) and fast-execution (press shift-enter to execute the search).

Normally your kJQ filters will start with .key .value or .header but you can search on any field returned with a Kafka record, including topic, offset, etc.

Kpow implements a subset of JQ allowing you to search JSON, Avro, Transit, EDN, String, and Custom Serdes with complex queries on structured data.

Language

kJQ filters can be applied to keys, values, and headers.

Kpow will scan tens of thousands of messages a second to find matching data.

kJQ is not whitespace sensitive.

kJQ Grammar

A kJQ filter is a limited version of a basic JQ filter.

Filters

A filter consists of a selector optionally followed by a transform then either a comparator or a function.

A filter can optionally be negated and joined with other filters with a logical operator.

Selectors

A selector is a JQ dot notation Object Index or zero-based Array Index.

e.g. .user.name, .[0], .transactions[1].amount

A selector can also be a quoted string or generic object index, just like normal JQ.

e.g. .user."first.name"" matches a key containing a period, i.e. {"user": {"first.name": 1}}

e.g .user.[:status_code] matches an explicit Clojure keyword {"user" {:status_code 2}}

Simple dot notation selectors match both String or (Clojure) Keyword keys.

Transforms

A transform converts the value of a field, often in use with a comparator or function.

Valid transforms: length, to-long, to-double, from-date, min, max

E.g. | to-long, | min

Comparators

A comparator is an operator followed by a selector or a scalar.

Valid operators: ==, !=, <, <=, >, >=

e.g. >= 10, != false, == "text", == nil, != null, < .tx.baz

Function

A function is a pipe followed by a function-name with text, keyword, number, or regex parameter.

Valid function names: startswith, endswith, inside, has, test, within, contains

e.g. | test(".*tx"), | startswith("text"), | endswith("text"), | contains("text")

kJQ Query Evaluation

Multiple kJQ filters can be joined with a logical AND or OR, just like normal JQ.

kJQ also supports standard explicit logical operator precedence with parenthesis.

e.g. (.key.id or .key.currency == "GBP) and .value.tx.discount | to-double < 20.20

kJQ Query Negation

A kJQ query filter can be negated. Negation can be applied to logically combined filters.

e.g. | not

Examples

Truthy Filter

.value.tx.status

Matches where the selector is not null.

E.g { "tx": { "status": true }} or { "tx": { "status": 1 }} will match, { "meta": { "status": true }} will not match

Scalar Comparator Filter

.value.tx.amount > 10

Matches where the selector > 10

E.g. { "tx": { "amount": 11 }} will match, { "tx": { "amount": 8 }} will not

Selector Comparator Filter

.value.tx.amount == .value.tx.discount

Matches where both selectors are equal.

E.g. { "tx": { "amount": 10, "discount": 10 }} will match, { "tx": { "amount": 10, "discount": 7 }} will not

Function Filter

.value.tx.labels[0] | contains("URGENT")

Matches where the selector contains text

E.g. { "tx": { "labels": ["URGENT-PENDING"] }} will match, { "tx": { "labels": ["PENDING"] }} will not.

Regex Tests

Just like JQ you can test if a regex matches a field

.key.id | test(".*tx")

True when the .key.id matches the regex #.*tx

Negated Filter

.[0].tx.status | contains("PENDING") | not

Matches where the selector does not contain text.

Quoted and Clojure Selectors / Scalars

.value."price.with.tax" > 10 and
.value."category!" == :seasonal

kJQ understands quoted and Clojure data

Multiple Filters (And)

.value.tx.amount > 10 and
.value.tx.amount == .value.tx.discount and
.value.tx.labels[0] | contains("URGENT")

Matches where every filter is true.

Multiple Filters (Or)

.value.tx.amount == .value.tx.discount or
.value.tx.labels[0] | contains("URGENT")

Matches where any filter is true.

Multiple Filters (Mixed)

Combine multiple filters with and, or, and explicit precedence.

(.key.currency == "GBP" and
 .value.tx.price | to-double < 16.50 and
 .value.tx.pan | endswith("8649")) or 
(.key.currency == "GBP" and 
 .value.tx.discount == "3.98")

Date Filtering with from-date

kJQ supports converting field values into ISO-8601 timestamp strings using the from-date transform. This includes:

  • AVRO logicalType date fields (encoded as int = days since epoch)

  • UNIX timestamps (number of seconds or milliseconds since epoch)

  • ISO-8601 formatted strings

To compare against literal date strings, prefix them with #dt to coerce into a timestamp.

Basic usage

.value.tx.start_date | from-date > #dt "2023-01-01T00:00:00Z"

Matches records where .value.tx.start_date is before or on May 10th, 2025.

.value.tx.start_date | from-date >= #dt "2023-01-01T00:00:00Z" and .value.tx.start_date | from-date <= #dt "2023-12-31T00:00:00Z"

Matches records where .value.tx.start_date falls within 2023 (inclusive).

.value.tx.start_date | from-date < #dt "2023-03-01T00:00:00Z"

Matches records before March 1st, 2023.

Filtering by Record Size

kJQ supports filtering based on metadata fields such as record size, key size, and value size.

Record size

.size > 1500

Matches records whose total serialized size exceeds 1500 bytes.

Key size

.key-size | to-long < 500

Matches records with key payloads under 500 bytes.

Value size

.value-size >= 1024

Matches records where the value is at least 1KB in size.

These metadata fields are always available in the record envelope, regardless of key/value format.

Combined size

(.key-size + .value-size) < 30

Filters records where the total key + value size is less than 30 bytes.

Null checks

.key == null

Filters records that have no key (null keys).