The defect says: «the discount never arrives». The response holds a discount field set to null; the developer answers «correct, there is no discount» and closes it. A week later the same check goes red on another environment, and there the discount field is missing entirely: two different answers, described in the same words.
Data reaches a tester in four wrappings: JSON from the API, XML from older exchanges, YAML in environment settings, CSV in an export. The meaning is one, the reading rules differ — and a fair share of misses grows not out of application logic but out of how the wrapping was read.
Order 42 for 4990, paid, with no promo code — in four formats. The meaning is the same everywhere, but in JSON every value carries a type, in XML the number moved into an attribute, in YAML the empty value is written as «~», and in CSV you cannot see it at all — only the extra delimiter at the end of the row speaks for it.
JSON: value types and two kinds of empty
A JSON response looks like text, yet it has to be checked by values — and quotes often decide the outcome.
There are six kinds of value: an object in braces, an array in brackets, a string in double quotes, a number without quotes, true/false and null. Single quotes and a trailing comma after the last field are forbidden. The difference between "42" and 42 is not cosmetic: a string sorts alphabetically ("10" before "9") and is often rejected on input. There are no types for dates and money: a date travels as the string "2026-03-01T10:00:00Z", an amount as a number, and whether that number is dollars or cents is stated only in the service description.
Nesting gives the path to a value: order.customer.name, and an array adds an index to that path — if the service never promised an order, the check «the first element is the right one» will keep flapping.
Emptiness comes in two kinds, and they are different stories.
live example
import json
response = json.loads('{"id": 42, "discount": null}')
print("discount" in response, response.get("discount"))
print("comment" in response, response.get("comment"))
Run
Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Free week →
This prints True None and False None. The discount field is present and empty — the discount was calculated and came out as «nothing». The comment field is absent altogether: a different version, missing rights, never filled in. Looking a key up returns the same thing in both cases, so the difference is easy to lose — and in a bug report it is spelled out in words.
JSON Schema: required fields in a list
«Is phone required?», «which values can status take?» — these are not answered from memory. The answer lives in the schema, a description of the shape of the response.
{
"type": "object",
"required": ["id", "total", "status"],
"properties": {
"id": {"type": "integer"},
"total": {"type": "number", "minimum": 0},
"status": {"enum": ["NEW", "PAID", "CANCELLED"]}
}
}
For checks this is ready material. required is a list of negative checks: drop the field and expect a refusal. type is that same difference between 42 and "42". enum gives equivalence classes with nothing to invent: three values inside and any fourth one outside. minimum is a boundary with the values −1, 0 and 1. The schema is rarely hunted separately: it sits inside the OpenAPI description, and Postman validates a response against it right inside a check.
What a schema cannot do: {"id": 42, "total": 0, "status": "PAID"} matches it completely, while in business terms it is a paid order for zero. The schema catches the shape; the meaning stays with the tester.
JSONPath: the address of a field
When a response is nested three levels deep, the field still has to be named: in a report, in a check, in conversation. That is what JSONPath is for: $.orders[0].customer.name. Here $ is the root, a dot is a step inward, [0] is an array element by index, [*] is all of them at once: $.orders[*].total collects every amount. Postman and jq understand the same path, so a vague «the name is empty» turns into an exact address.
XML: attribute or element
The same order in XML comes out twice as long, and one value can be written in two ways.
<order id="42">
<total currency="RUB">4990</total>
</order>
id and currency are attributes, total is an element. An attribute holds a single value and nests nothing; an element can repeat and can contain others. Which is which was decided by the service itself, and the path to the value differs: /order/@id versus /order/total.
A prefix such as soap: or ns2: is a namespace declared next to it via xmlns, and it is part of the name: <ns2:total> and <total> are different elements to a parser. Hence the classic «I can see the field with my own eyes, but the check says it is not there».
XML is stricter than JSON: an unclosed tag is not a lost field but an unreadable response as a whole. The schema language for XML is called XSD.
YAML: where it lies quietly
YAML is read as «plain text with colons», and yet it holds environment settings, docker-compose and build pipelines. The price of a mistake is not a failed parse but quietly a different value.
database:
host: db.test
port: 5432
ssl: no
version: 1.0
Nesting is set by indentation, and by spaces only — tabs are forbidden. A line shifted by two spaces moves under the neighbouring key while the file stays valid: there will be no error, there will be a different setting.
ssl: no is not the string «no» but false: parsers following version 1.1, and most of them do, read yes, no, on, off as booleans. The country code NO stumbles in the same place — Norway turns into false. And version: 1.0 is a number equal to 1, so it will never match the string "1.0". The cure is the same in both cases, quotes: "no", "1.0".
CSV: why the export breaks
The export is opened in Excel: instead of a table there is one column, instead of names «Ð˜Ð²Ð°Ð½», and a product code shown as 4.99E+11. There are three reasons, and none of them is the application.
The first is the delimiter. The name of the format promises a comma, and RFC 4180 describes one too, but nothing obliges an export to follow that document: Excel in many locales expects a semicolon, because the comma is taken by the decimal part. A delimiter is something the two sides agree on in advance.
The second is quoting. A value containing a delimiter, a line break or a quote is wrapped in double quotes, and the inner quote is doubled.
id;name
42;"Acme ""Trading"", London"
The second row holds two fields: 42 and Acme "Trading", London — the comma inside the quotes stayed data. An export assembled by gluing values with ; and no quoting will slide a column at the first address containing a comma.
The third is encoding. UTF-8 without a BOM is read by Excel on Windows as the system's single-byte code page: the same bytes add up to different letters, and «Иван» turns into «Ð˜Ð²Ð°Ð½». A BOM at the start of the file helps, or importing through «Data → From Text».
Excel also edits values on its own: 00123 becomes 123, 1-2 becomes a date. So «the product codes broke in the export» is first checked in a text editor: if the file is intact, it is the view that is broken, not the data.
Where each format shows up
The format is not chosen by the tester but by the system on the other end — and the source makes it predictable.
| Format | Where you meet it | What opens it |
|---|---|---|
| JSON | REST and GraphQL responses, logs | Postman, DevTools, jq |
| XML | SOAP, banking and government exchanges | SoapUI, Postman |
| YAML | environment settings, docker-compose | any editor with highlighting |
| CSV | reports, price lists, reference data | a text editor, then Excel |
In short
- In JSON the type is visible in the notation:
"42"is a string,42is a number; there are no types for dates and money. - A field set to
nulland a missing field are two different answers, and they are named differently. - JSON Schema hands you checks:
requiredfor negative ones,typefor types,enumfor equivalence classes,minimumfor boundaries; meaning it does not catch. - In XML a value can be an attribute or an element, and the path differs; a namespace prefix is part of the name.
- YAML fails quietly: indentation changes the owner of a key,
nobecomesfalse,1.0becomes a number; quotes cure both. - CSV breaks on the delimiter, quoting and encoding — open the file in a text editor before raising a defect.
What to read next
- API styles — which style delivers which format.
- API testing in Postman — taking a response apart field by field.
- Test design techniques — what grows out of
enumand boundaries. - SQL for testers — the same data straight from the database.