Validating JSON Shape with Karate's Fuzzy Match Markers
Learn how Karate's fuzzy match markers (#string, #number, ##string, #[] ...) let you validate JSON response shape without brittle full‑payload equality, plus a worked example, trade‑offs, and a quick verification checklist.
04 Aug 2025, 10:18 UTC

The problem: brittle full‑payload assertions
When an API returns timestamps, generated IDs, or optional fields, a strict match response == expected breaks on every deploy. Teams either duplicate large JSON fixtures or relax the test to the point of uselessness. Karate’s match keyword solves this by letting you describe the shape of a response instead of its exact values.
Fuzzy type markers you can use today
#string,#number,#boolean,#null– assert the field exists and has the given primitive type.#notnull– field present and notnull(any type).#present– field present (value may benull).##string(double‑hash) – optional field; if it appears it must be a string.#[] #stringor#[] { id: #number, name: #string }– every array element matches the element schema.#(expr)or#? _ > 0– embed a JavaScript expression or predicate for range/format checks.
All markers are evaluated at runtime by Karate 1.x (tested on 1.4.0). The syntax is stable across the 1.x line, but always verify against the version you ship.
Worked example: a paginated user list
Assume GET /api/users?page=1 returns:
{
"page": 1,
"pageSize": 20,
"total": 237,
"items": [
{ "id": 101, "email": "[contact removed]", "active": true },
{ "id": 102, "email": "[contact removed]", "active": false }
],
"nextCursor": "eyJwYWdlIjoyfQ=="
}
Instead of hard‑coding IDs and the cursor, define a reusable schema in the feature file:
Feature: User list pagination
Background:
* url 'https://api.example.com'
* def userSchema = { id: '#number', email: '#string', active: '#boolean' }
* def pageSchema = {
page: '#number',
pageSize: '#number',
total: '#number',
items: '#[] userSchema',
nextCursor: '##string'
}
Scenario: First page returns expected shape
Given path 'api', 'users'
And param page = 1
When method get
Then status 200
And match response == pageSchema
Running this with mvn test -Dtest=UserListTest (or the Karate standalone JAR) produces a readable diff if any field violates the schema – e.g., if active becomes a string the test fails with a clear message pointing at items[0].active.
Trade‑offs and limitations
- Shape ≠ correctness. A response where
emailis"not-an-email"still passes because#stringonly checks type. Add a targeted predicate when format matters:email: '#? _ =~ /^.+@.+\..+$/'. - Over‑loose schemas give false confidence. Using
#presentfor every field lets contract regressions (missing required fields) slip through. Keep required fields strict (#string,#number) and reserve##for truly optional data. - Version sensitivity. Marker semantics changed between 0.9.x and 1.x (e.g.,
##was introduced in 1.0). Always run a quick sanity test after upgrading Karate.
How to verify the approach in your repo
- Create a minimal feature file that hits a stable endpoint (or a local mock) and asserts
match response == { items: '#[] { id: #number, name: #string }' }. - Run the suite (
mvn testorjava -jar karate.jar) and confirm a green result. - Intentionally break the contract – e.g., change the mock to return
id: "oops"– and rerun. The failure diff should pinpoint the type mismatch. - If the diff is readable and the test fails as expected, the schema is doing its job.
Closing action
Replace one fragile full‑payload assertion in your suite with a fuzzy schema today. Start with a single endpoint, extract the schema into a def block, and run the verification steps above. Once the pattern feels natural, roll it out across the suite – you’ll spend less time updating fixtures and more time catching real contract changes.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.