JSON, XML and JSON Schema Validation for API Testers

JSON, XML and JSON Schema Validation for API Testers

Almost every API response you test will be JSON, and banking or SOAP systems will hand you XML. To write good assertions you must read nested data fluently, know the exact data types, and be able to prove the whole structure is correct, not just one field. In this lesson you will learn JSON and XML from a tester’s point of view, how to extract values with JSONPath and XPath, and how JSON Schema validation catches the bugs that field-by-field checks miss.

Quick answerJSON (JavaScript Object Notation) is a lightweight text format of key-value pairs with six data types: string, number, boolean, null, object and array. XML is a tag-based format used by SOAP and older enterprise systems. JSON Schema is a JSON document that describes the expected structure of JSON data (required fields, types, formats, ranges and allowed values). Testers validate responses against a schema to catch missing fields, wrong types and unexpected extra data automatically.
Advertisement

JSON Basics in 5 Minutes

Here is a real response from the practice API: GET /orders/5001.

{
  "data": {
    "id": 5001,
    "user_id": 1,
    "status": "delivered",
    "ordered_at": "2025-09-02T11:00:00Z",
    "items": [
      { "product_id": 101, "name": "Wireless Mouse", "quantity": 2, "unit_price": 799, "line_total": 1598 },
      { "product_id": 108, "name": "Python Testing eBook", "quantity": 1, "unit_price": 349, "line_total": 349 }
    ],
    "summary": { "item_count": 2, "subtotal": 1947, "tax": 350.46, "total": 2297.46, "currency": "INR" },
    "shipping": { "city": "Pune", "pincode": "411001", "country": "IN" }
  }
}
JSON typeExample from this responseTester’s note
String"delivered", "411001"Always double quotes. Pincode is a string (keeps leading zeros)
Number799, 350.46No quotes; integer and decimal are both “number”
Booleantrue / falseLower case, no quotes. "true" is a string, a common bug
Nullnull“No value”. Different from missing key and from ""
Object"summary": { ... }Unordered key-value pairs
Array"items": [ ... ]Ordered list; may be empty []
Real bug: “1947” vs 1947 After a backend upgrade, subtotal started coming back as the string "1947". The website still displayed it fine, but the Android app added the coupon discount with string concatenation and showed a subtotal of “1947100”. Only a type check (or schema validation) catches this kind of bug.

Reading Nested JSON: Paths

To assert a value you need its path. In JavaScript/TypeScript (Playwright) and in JSONPath:

ValueTypeScriptJSONPath
Order statusbody.data.status$.data.status
First item namebody.data.items[0].name$.data.items[0].name
All item namesbody.data.items.map(i => i.name)$.data.items[*].name
Grand totalbody.data.summary.total$.data.summary.total
Items costing over 500items.filter(i => i.unit_price > 500)$.data.items[?(@.unit_price > 500)]

Postman also uses this JavaScript syntax inside its test scripts: pm.response.json().data.status.

Advertisement

Assert Business Logic, Not Only Values

Good testers recompute numbers from the response itself:

  • Each line_total = quantity x unit_price (2 x 799 = 1598 ✔).
  • subtotal = sum of line totals (1598 + 349 = 1947 ✔).
  • tax = 18% of subtotal (1947 x 0.18 = 350.46 ✔).
  • total = subtotal + tax (2297.46 ✔).
  • item_count = length of items (2 ✔).
GEThttps://sqatools.in/wp-json/sqa-api/v1/orders/5001
curl -i \
  "https://sqatools.in/wp-json/sqa-api/v1/orders/5001"
Decimal trap In JavaScript, 0.1 + 0.2 === 0.30000000000000004. Compare money with a tolerance (toBeCloseTo(2297.46, 2)) or in paise as integers, otherwise your test fails for the wrong reason.

XML Basics for API Testers

Ask the practice API for XML using the Accept header:

GEThttps://sqatools.in/wp-json/sqa-api/v1/users/1
curl -i \
  -H "Accept: application/xml" \
  "https://sqatools.in/wp-json/sqa-api/v1/users/1"
<?xml version="1.0" encoding="UTF-8"?>
<response>
  <data>
    <id>1</id>
    <name>Asha Verma</name>
    <role>admin</role>
    <active>true</active>
  </data>
</response>

XML key concepts: elements (<name>), attributes (<price currency="INR">), namespaces (xmlns:soap="...", essential in SOAP), and every value is text until a schema (XSD) gives it a type.

XPath: extracting values from XML

GoalXPath
User name/response/data/name
Name anywhere in the document//name
Text value//name/text()
Element with an attribute//price[@currency='INR']
SOAP body ignoring namespaces//*[local-name()='AddResult']

SoapUI’s XPath Match assertion uses exactly these expressions; you will use them in the SoapUI lessons.

Advertisement

JSON vs XML

JSONXML
ReadabilityCompact, easy to readVerbose (opening and closing tags)
Data typesBuilt in (number, boolean, null…)Everything is text; types come from XSD
ArraysNative [ ]Repeated elements
Attributes / namespacesNoYes
CommentsNot allowedAllowed
SchemaJSON SchemaXSD
Query languageJSONPathXPath, XQuery
Used inREST, GraphQL, most modern APIsSOAP, banking, insurance, legacy systems, RSS

What Is JSON Schema?

A JSON Schema is a contract for JSON data. Instead of writing 30 assertions for 30 fields, you validate the whole response against one schema. The practice API publishes its schema for users:

GEThttps://sqatools.in/wp-json/sqa-api/v1/schema/user
curl -i \
  "https://sqatools.in/wp-json/sqa-api/v1/schema/user"
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "user",
  "type": "object",
  "required": ["id", "name", "username", "email", "role", "city", "age", "active", "created_at"],
  "properties": {
    "id":         { "type": "integer", "minimum": 1 },
    "name":       { "type": "string" },
    "email":      { "type": "string", "format": "email" },
    "role":       { "enum": ["admin", "editor", "viewer"] },
    "age":        { "type": "integer", "minimum": 18 },
    "active":     { "type": "boolean" },
    "created_at": { "type": "string", "format": "date-time" }
  },
  "additionalProperties": false
}

Important JSON Schema keywords

KeywordMeaningBug it catches
typestring, number, integer, boolean, object, array, null“1947” instead of 1947
requiredKeys that must existField silently removed
enumAllowed valuesNew status “dispatched” the app doesn’t know
formatemail, date-time, uri, uuid…Date returned as “12/11/2026”
minimum / maximumNumber rangeNegative price, age 0
minLength / maxLength / patternString rulesPhone with letters
items, minItemsArray content and sizeOrder with zero items
additionalProperties: falseNo unknown fieldsLeaked password_hash or internal fields
nullable / "type": ["string","null"]Allow nullUnexpected null crashing the app
Advertisement

JSON Schema Validation in Playwright (with Ajv)

Install the most popular validator: npm i -D ajv ajv-formats.

import { test, expect } from '@playwright/test';
import Ajv from 'ajv';
import addFormats from 'ajv-formats';

const BASE = 'https://sqatools.in/wp-json/sqa-api/v1';
const ajv = new Ajv({ allErrors: true });
addFormats(ajv);

test('every user matches the published JSON Schema', async ({ request }) => {
  // 1. Get the contract from the API itself
  const schema = await (await request.get(`${BASE}/schema/user`)).json();
  const validate = ajv.compile(schema);

  // 2. Validate all 12 users
  const users = (await (await request.get(`${BASE}/users`, { params: { per_page: 50 } })).json()).data;
  for (const user of users) {
    const ok = validate(user);
    expect(ok, JSON.stringify(validate.errors, null, 2)).toBe(true);
  }
});

test('order totals are internally consistent', async ({ request }) => {
  const { data: order } = await (await request.get(`${BASE}/orders/5001`)).json();

  for (const item of order.items) {
    expect(item.line_total).toBe(item.quantity * item.unit_price);
  }
  const subtotal = order.items.reduce((sum: number, i: any) => sum + i.line_total, 0);
  expect(order.summary.subtotal).toBe(subtotal);
  expect(order.summary.tax).toBeCloseTo(subtotal * 0.18, 2);
  expect(order.summary.total).toBeCloseTo(subtotal + order.summary.tax, 2);
  expect(typeof order.shipping.pincode).toBe('string');
});

When validation fails, Ajv tells you exactly what broke, for example /age must be integer or must NOT have additional properties: password_hash. TypeScript teams also use Zod schemas, which double as TypeScript types; you will use both in Module 6.

Advertisement

JSON Testing Checklist

  • Response body is valid JSON (parsing doesn’t throw), even for errors.
  • Correct data types: numbers aren’t strings, booleans aren’t “true”.
  • Required fields present; no unexpected or sensitive fields.
  • Null vs missing vs empty string behave as documented.
  • Dates in one consistent format (ISO 8601 with time zone, e.g. 2025-09-02T11:00:00Z).
  • Arrays: empty list returns [], not null.
  • Calculated fields are correct; money compared safely.
  • Unicode (Hindi names, emojis) survives a round trip.
  • Malformed JSON in a request returns 400, not 500.

Try the last check now. This request sends broken JSON (the body stops halfway), and a well-built API must answer 400 with a clear message:

POSThttps://sqatools.in/wp-json/sqa-api/v1/validate/user
curl -i \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"name": "Asha", "email": ' \
  "https://sqatools.in/wp-json/sqa-api/v1/validate/user"
Advertisement

Common Mistakes

  • Comparing a whole response with a saved copy: timestamps and ids change, so tests flake. Validate structure with a schema and assert only the stable values.
  • Using toEqual on objects when extra fields are allowed: use toMatchObject for partial matching.
  • Assuming key order: JSON objects are unordered.
  • Assuming array order when the API doesn’t promise one.
  • Ignoring additionalProperties: that’s how data leaks go unnoticed.
Advertisement

Interview Questions: JSON, XML and Schema

What are the data types supported by JSON?

String, number, boolean, null, object and array. There is no separate integer or date type in JSON itself; integers are numbers and dates are strings, which is why JSON Schema adds integer and format rules.

What is JSON Schema validation and why is it used?

It validates a JSON response against a schema that defines required fields, types, formats, ranges, enums and whether extra properties are allowed. It catches structural bugs such as missing fields, wrong types and leaked data in one assertion and is the basis of contract testing.

What is the difference between JSON and XML?

JSON is lighter, has native data types and arrays and is used by most modern REST APIs. XML is more verbose, supports attributes, namespaces and comments, uses XSD for types and XPath for queries, and is used by SOAP and many enterprise systems.

What is JSONPath?

A query language for JSON, similar to XPath for XML. For example, $.data.items[*].name returns all item names and $.data.items[?(@.unit_price > 500)] filters items by price.

What is the difference between null, an empty string and a missing key?

A missing key means the field isn’t sent at all, null means the field exists with no value, and an empty string is a value of length zero. Clients often handle them differently, so APIs must be consistent and tests should check the documented behaviour.

What does additionalProperties: false do?

It makes the schema reject any property not listed in properties. It helps detect unexpected fields, especially sensitive data such as password hashes or internal ids accidentally included in responses.

How do you validate an XML response?

Validate it against the XSD, use XPath assertions for specific values, check namespaces, and for SOAP verify there is no Fault element in positive scenarios. SoapUI provides Schema Compliance and XPath Match assertions for this.

How would you compare decimal values like prices in tests?

Use a tolerance-based comparison such as toBeCloseTo with 2 decimal places, or convert money to the smallest unit (paise) as integers, to avoid floating point precision errors.

Which library do you use for JSON Schema validation in TypeScript?

Ajv with ajv-formats is the most common choice. Zod is also popular because the same schema provides TypeScript types and runtime validation.

Why should you avoid comparing full responses with snapshots?

Dynamic values such as ids, timestamps and tokens change on every run, causing flaky failures. It’s better to validate structure with a schema and assert specific stable business values.

Advertisement
View the full API testing syllabus