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.
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).