Validating Nested JSON

A Nested JSON response contains JSON objects inside other JSON objects or arrays.

Most real-world REST APIs return nested JSON structures, so API automation must validate values located deep inside the response.

REST Assured uses JsonPath with dot notation to navigate nested objects.


Advertisement

Interview Answer

"Nested JSON validation means verifying values that are present inside child objects or arrays. In REST Assured, I use JsonPath expressions such as user.address.city or users[0].role.name together with body() assertions to validate nested values."


Nested JSON Example

{
  "user": {
    "id": 101,
    "name": "John",
    "address": {
      "city": "Hyderabad",
      "pincode": 500001
    }
  }
}

JSON Structure

user
│
├── id
├── name
└── address
      │
      ├── city
      └── pincode

Validate Nested Fields

given()

.when()
        .get("/users/101")

.then()
        .statusCode(200)
        .body("user.address.city", equalTo("Hyderabad"))
        .body("user.address.pincode", equalTo(500001));

Nested Array Example

{
   "users":[
      {
         "id":101,
         "name":"John"
      },
      {
         "id":102,
         "name":"David"
      }
   ]
}

Validate Nested Array

.body("users[0].name", equalTo("John"))
.body("users[1].id", equalTo(102));

Real-Time Example

In my project, we validated nested fields such as city, pincode, and role name, which were present inside multiple levels of nested JSON objects returned by the API.


Best Practices

  • Use dot notation for nested objects.
  • Use array indexes for array elements.
  • Combine nested validation with status code validation.
  • Validate mandatory nested fields using notNullValue().

Validating Array Responses

Many APIs return arrays instead of a single object.

Array validation ensures:

  • Correct number of elements
  • Expected values exist
  • Correct values at specific indexes

Sample Array Response

{
  "users": [
    {
      "id": 101,
      "name": "John"
    },
    {
      "id": 102,
      "name": "David"
    },
    {
      "id": 103,
      "name": "Rahul"
    }
  ]
}

Validate Array Size

.body("users", hasSize(3));

Validate Value at Index

.body("users[0].name", equalTo("John"));

Validate Existing Value

.body("users.name", hasItem("David"));

Validate Multiple Values

.body("users.name",
      hasItems("John", "Rahul"));

Array Validation Flow

Array Response
      │
      ▼
Validate Size
      │
      ▼
Validate Index
      │
      ▼
Validate Expected Values

Real-Time Example

In our automation framework, we validated the total number of users returned by the API, verified IDs at specific indexes, and confirmed that expected roles existed in the response array.


Best Practices

  • Validate array size.
  • Validate important indexes.
  • Validate expected values using hasItem().
  • Avoid relying on indexes when the order is not guaranteed.

Validating Dynamic Values

Some API values change every time the API is executed.

Examples:

  • User ID
  • Order ID
  • JWT Token
  • Timestamp
  • UUID

These values should not be validated using fixed values.


Common Dynamic Matchers

Matcher Purpose
notNullValue() Value exists
greaterThan() Numeric comparison
lessThan() Numeric comparison
matchesRegex() Pattern validation

Validate Dynamic ID

.body("id",
      greaterThan(0));

Validate Token

.body("token",
      notNullValue());

Validate Timestamp

.body("createdAt",
      notNullValue());

Validate Using Regex

.body("email",
      matchesRegex(".+@.+\\..+"));

Extract and Validate

int id =
response.jsonPath()
        .getInt("id");

Assert.assertTrue(id > 0);

Real-Time Example

In my project, Order IDs and timestamps were generated dynamically. Instead of comparing fixed values, we validated that the Order ID was greater than zero and that the timestamp was present using notNullValue().


Best Practices

  • Never hardcode dynamic values.
  • Use matchers such as notNullValue() and greaterThan().
  • Extract values when additional custom validation is required.

Handling Null Values

Some API fields are optional and may legitimately return null.

REST Assured provides matchers to validate whether fields are null or not.


Validate Null Value

.body("middleName",
      nullValue());

Validate Mandatory Field

.body("email",
      notNullValue());

Null Validation Table

Scenario Matcher
Optional Field nullValue()
Mandatory Field notNullValue()

Real-Time Example

We validated optional fields such as middleName using nullValue() and mandatory fields such as userId and email using notNullValue(). Unexpected null values were treated as defects.


Best Practices

  • Clearly identify optional fields.
  • Validate mandatory fields using notNullValue().
  • Log defects when mandatory values are unexpectedly null.

Asserting Response Headers

Response headers provide metadata about the API response.

REST Assured allows validation of both individual headers and multiple headers.


Common Response Headers

  • Content-Type
  • Cache-Control
  • Content-Encoding
  • Server
  • Date
  • Custom Security Headers

Validate Single Header

response.then()
        .header("Content-Type",
                "application/json");

Validate Multiple Headers

response.then()
        .headers(
            "Content-Type",
            "application/json",

            "Cache-Control",
            "no-cache"
        );

Watch out: many servers send application/json; charset=utf-8, so an exact match on Content-Type fails. Use a matcher instead: .header("Content-Type", containsString("application/json")) or .contentType(ContentType.JSON).


Real-Time Example

In my project, every API was validated to ensure it returned application/json as the Content-Type. We also verified security-related headers such as Cache-Control and reported missing headers as defects.


Best Practices

  • Validate Content-Type.
  • Validate security headers.
  • Validate caching headers when applicable.
  • Verify custom headers for business requirements.

Asserting Cookies

Some applications use cookies to maintain user sessions.

REST Assured allows validation of cookies returned by the API.


response.then()
        .cookie("JSESSIONID");

response.then()
        .cookie("USER", "John");

API Response
      │
      ▼
Cookie Returned?
      │
      ▼
Validate Cookie
      │
      ▼
Validate Cookie Value

Real-Time Example

During session-based authentication testing, we verified that the server returned the expected session cookie and that its value matched the authenticated user session.


Extracting a Value and Chaining Requests

Real-world API workflows often depend on values returned by previous API calls.

Examples:

  • Login → Token
  • Create User → User ID
  • Create Order → Order ID

These values are extracted and reused in subsequent requests.


Chaining Flow

Login API
     │
     ▼
Extract Token
     │
     ▼
Create User API
     │
     ▼
Extract User ID
     │
     ▼
Update User API
     │
     ▼
Get User API

Extract Token

String token =

response.jsonPath()
        .getString("token");

Pass Token

given()
        .header("Authorization",
                "Bearer " + token)

Extract ID

int id =
response.jsonPath()
        .getInt("id");

Pass Path Parameter

given()
        .pathParam("id", id)

.when()
        .get("/users/{id}");

Real-Time Example

In our framework, the Login API generated a JWT token, which we extracted using JsonPath and reused in the Authorization header. Similarly, after creating a user, we extracted the generated User ID and passed it as a path parameter to the Update User and Get User APIs, enabling complete end-to-end API automation.


Best Practices

  • Extract dynamic values immediately after receiving the response.
  • Reuse extracted values instead of hardcoding them.
  • Validate extracted values before using them in subsequent requests.
  • Use JsonPath expressions that clearly reflect the response structure.
  • Combine extraction with response validation to ensure data correctness.

📚 Official documentation: REST Assured official site · MDN: HTTP

Frequently Asked Questions (FAQs)

1. How do you validate nested JSON?

I use JsonPath dot notation such as user.address.city or users[0].role.name inside body() assertions to navigate and validate nested JSON values.


2. How do you validate an array response?

I validate:

  • Array size using hasSize()
  • Values at specific indexes such as users[0].name
  • Presence of expected values using hasItem() or hasItems()

This ensures both the structure and content of the array are correct.


3. How do you validate dynamic values like IDs and timestamps?

Since these values change for every execution, I avoid comparing fixed values. Instead, I use matchers such as:

  • notNullValue()
  • greaterThan()
  • matchesRegex()

or extract the values using JsonPath and perform conditional validations.


4. How do you handle null values in a response?

I validate optional fields using nullValue() and mandatory fields using notNullValue(). If a mandatory field unexpectedly returns null, the test fails and the issue is reported as a defect.


5. How do you assert response headers?

I use:

  • header() to validate a single response header
  • headers() to validate multiple headers

Common validations include Content-Type, Cache-Control, Content-Encoding, and other security-related headers.


6. How do you chain requests?

I extract dynamic values such as tokens or IDs using JsonPath, for example:

response.jsonPath().getString("token")

or

response.jsonPath().getInt("id")

Then I reuse those values in subsequent requests as:

  • Authorization headers
  • Path parameters
  • Query parameters
  • Request body values

This approach enables complete end-to-end API workflow automation.


More Powerful Array and Object Checks

Using the same users array as above (with an active flag and an amount per user), GPath and Hamcrest can check whole collections in one line:

.body("users.id", everyItem(greaterThan(0)))                     // every element, not just one index
.body("users.name", not(hasItem(emptyOrNullString())))            // no blank names
.body("users.find { it.id == 102 }.name", equalTo("David"))       // search by value, not position
.body("users.findAll { it.active }.size()", equalTo(2))           // count matching elements
.body("users.amount.sum()", equalTo(1500))                        // aggregate values
.body("users[0]", hasKey("email"))                                // a field exists (even if null)
.body("users.id", containsInAnyOrder(101, 102, 103))              // exact set, order ignored

find { } is more robust than an index like users[1], which breaks as soon as the API changes its sort order. For the overall structure — required fields and types on every element — add a JSON schema validation.

Checking a timestamp's format, not just that it exists

String createdAt = response.path("createdAt");
Instant created = Instant.parse(createdAt);                           // fails if not ISO-8601
Assert.assertTrue(Duration.between(created, Instant.now()).abs().toMinutes() < 5,
        "createdAt should be recent");

notNullValue() only proves the field is there; parsing it proves the format, and comparing with the current time proves the value makes sense.

Watch JsonPath expressions pick values out of a nested response step by step in the REST Assured Visualizer.

From Real Projects

My API experience covers CRUD operations, HTTP methods, JSON path, and validating JSON and XML responses, and my automation work was in Java with TestNG. That combination is exactly what REST Assured builds on: HTTP calls in Java, JSON path for extracting values, and TestNG for running and grouping the tests. If you already know Selenium with TestNG, REST Assured fits into the same project structure. Testsigma supports web, mobile and API test automation, so understanding APIs was part of understanding the product I was testing. Combine value checks with schema validation for complete coverage.