← back to section

One variable holds one value. A service response is not one value but a set: an order with a number, a total and a status, plus a list of items. The language has two containers for such sets, and almost all of a tester's work is work with them.

A list: a sequence in order

live example

statuses = ["NEW", "PAID", "SHIPPED"]
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 →

Square brackets, values separated by commas. Elements are numbered from zero — the main beginner trap:

print(statuses[0])     # NEW   — the first
print(statuses[-1])    # SHIPPED — the last
print(len(statuses))   # 3

Add and check:

statuses.append("DELIVERED")
print("PAID" in statuses)        # True

Asking for an element that does not exist gives IndexError: list index out of range. A useful error: it means there was less data than you expected — a common finding in tests.

A dict: a value by name

live example

order = {
    "id": "ord-042",
    "status": "PAID",
    "total": 4990,
}

print(order["status"])     # PAID
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 is exactly what JSON is. A JSON response, once parsed, becomes a Python dict one to one: same keys, same values. So "read a field from the response" is just response["status"].

A field that may be missing

This is where most tests break. Asking for a missing key is an error:

print(order["discount"])     # KeyError: 'discount'

The test fails not because it found a defect but because it is poorly written. Two safe ways:

if "discount" in order:
    print(order["discount"])

print(order.get("discount"))       # None when absent
print(order.get("discount", 0))    # 0 when absent

get() is the workhorse — but careful: it hides the absence. If the contract says the field must be there, check for it explicitly, otherwise your test stays silent about a real defect.

Nesting

A real response looks about like this:

live example

response = {
    "id": "ord-042",
    "customer": {"id": "cus-01", "name": "Anna"},
    "items": [
        {"product": "Grinder", "qty": 1, "price": 4990},
        {"product": "Lunchbox", "qty": 2, "price": 890},
    ],
    "total": 6770,
}

response["customer"]["name"]        # "Anna"
response["items"][0]["product"]     # "Grinder"
len(response["items"])              # 2
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 →

Every bracket is one step deeper. When you get lost, split it into variables: it reads worse than one line, but you can see where it broke when it breaks.

List or dict

The rule is simple: a list when the elements are of one kind and order matters (order items, found records); a dict when every value has its own name (object fields).

What to solve

In short

  • A list keeps order and is indexed from zero; [-1] is the last element.
  • A dict maps name to value; parsed JSON is a dict, which is why reading a field looks like response["status"].
  • A missing key raises KeyError; use get() or an in check — but verify contract-required fields explicitly.
  • Nesting reads step by step: response["items"][0]["product"].
  • Loops — how to walk over every item of an order.
  • Functions — how to keep a check in one place.