JSONPath and Response Extraction

9 min read

Every response Karate receives is automatically parsed and stored in the response variable. For a JSON API, response is a JavaScript object you can navigate with dot notation, store fields in variables, and use those variables in subsequent requests or assertions. This lesson covers the full extraction toolkit: dot notation, def, array access, Karate's built-in JavaScript functions, and the patterns for chaining multi-step workflows.

Dot notation — walking into JSON

Karate uses JavaScript-style dot notation to navigate response fields. No separate JSONPath library, no $ prefix:

# Response: { "id": 1, "name": "Alice", "address": { "city": "London" } }
* def userId = response.id
* def userName = response.name
* def city = response.address.city

For the same response, traditional JSONPath would be $.id, $.name, $.address.city. Karate's dot notation is shorter and reads more naturally. Under the hood Karate translates it to JSONPath, but you never write the $ prefix in feature files.

Array access by index:

# Response: { "users": [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}] }
* def firstUser = response.users[0]
* def secondName = response.users[1].name
* def lastUser = response.users[response.users.length - 1]

Negative indexing (response.users[-1]) is not supported by default — use response.users.length - 1 for the last element.

def — storing values for later

def is how you extract values from a response and carry them into subsequent steps:

Scenario: Create a user then verify they exist
  Given path 'users'
  And request { name: 'Alice', email: 'alice@test.com', role: 'admin' }
  When method post
  Then status 201
  * def userId = response.id
  * def userEmail = response.email
 
  Given path 'users', userId
  When method get
  Then status 200
  And match response.email == userEmail
  And match response.id == userId

userId and userEmail are plain JavaScript values — strings, numbers, booleans, objects, or arrays depending on what the field contains. They're available anywhere below their def line in the same scenario.

You can also define variables using JavaScript expressions:

* def fullName = response.firstName + ' ' + response.lastName
* def isAdmin = response.role == 'admin'
* def userCount = response.users.length

Extracting from arrays

When the response is an array, use array methods to filter or transform:

# Response is an array of users
* def users = response
* def adminUsers = karate.filter(users, function(u){ return u.role == 'admin' })
* def userNames = karate.map(users, function(u){ return u.name })
* def adminCount = karate.sizeOf(adminUsers)
 
And match adminCount > 0
And match userNames contains 'Alice'

karate.filter() returns a subset of the array matching the predicate. karate.map() transforms each element. karate.sizeOf() returns the length of an array or object. These are JavaScript-style — if you've written any JavaScript, they'll feel familiar.

For a quick count without extracting the array:

And match response == '#[_ > 0]'

This asserts the response is an array with at least one element, without storing anything.

Using karate.jsonPath() for complex paths

For paths that are hard to express with dot notation — particularly when a field name contains dots or special characters — use karate.jsonPath() explicitly:

* def val = karate.jsonPath(response, '$.meta[0].page.total')

This is the escape hatch for unusual response structures. For normal APIs with sensible field names, dot notation is always cleaner.

Dynamic values from the system

Sometimes a test needs a value that isn't in the response — a timestamp, a random string, or a counter. Karate allows JavaScript expressions in def:

* def timestamp = java.lang.System.currentTimeMillis()
* def uniqueEmail = 'test-' + timestamp + '@test.com'
* def randomId = Math.floor(Math.random() * 100000)

java.lang.System.currentTimeMillis() calls Java directly from Karate — useful for generating unique test data that won't collide across parallel runs.

The extraction and reuse flow

⚠️ Common mistakes

  • Using $ JSONPath syntax in match expressions. Karate uses dot notation, not $-prefixed JSONPath. Writing match $.users[0].name == 'Alice' is a syntax error. Write match response.users[0].name == 'Alice'. Reserve karate.jsonPath() for the rare cases where dot notation can't express the path.
  • Trying to use a def variable across scenarios. Variables defined with * def in Scenario 1 are not visible in Scenario 2. Each scenario gets a fresh scope (plus Background). If you need a shared value, define it in Background (for static values) or use a call to a setup feature file (for dynamic values like auth tokens).
  • Calling karate.filter() and expecting it to mutate the original array. karate.filter() returns a new array — the original is unchanged. Always assign the result: * def admins = karate.filter(response, function(u){ return u.role == 'admin' }).

🎯 Practice task

Practice extraction and chaining against JSONPlaceholder. 35–45 minutes.

  1. GET /users/1. Use * def to extract id, name, and email into three separate variables. Then write three separate match assertions that compare response.id == id, response.name == name, response.email == email. Run green — it's asserting against itself, but it proves extraction works.
  2. GET /users (returns an array). Extract the array into * def users = response. Use karate.sizeOf(users) to assert there are exactly 10 users. Use karate.map(users, function(u){ return u.id }) to get a list of IDs and assert match ids == '#array'.
  3. GET /users and use karate.filter() to find all users whose address.city starts with 'S'. Store the count and assert it is greater than 0 with a match statement.
  4. Write a chained scenario: POST /users (store response.id as userId), then GET /users/{userId} and assert response.id == userId. JSONPlaceholder returns fake data — the second GET won't find the POST (the API is stateless), so the GET will return {}. That's fine; the goal is to practice the chaining pattern, not validate persistence.
  5. Dynamic data: define * def uniqueEmail = 'user-' + java.lang.System.currentTimeMillis() + '@test.com'. POST a new user with that email. Assert response.email == uniqueEmail (if the API echoes it back). This is the pattern for generating collision-free test data.
  6. Stretch: GET /posts?userId=1 (JSONPlaceholder posts for user 1). Extract response[0].title and assert it is a non-empty string using match firstTitle == '#? _.length > 0'.

Next lesson: schema validation with Karate's match syntax — validating entire response shapes with inline markers, reusable schema objects, and schema files.

// tip to track lessons you complete and pick up where you left off across devices.