← back to section

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

A list: a sequence in order

A list is values in order, in square brackets, separated by commas. You take them by index, and indexing starts at zero — the main beginner trap:

live example

statuses = ["NEW", "PAID", "SHIPPED"]

print(statuses[0])          # NEW — the first one, not the zeroth
print(statuses[-1])         # SHIPPED — the last one, counting backwards
print(len(statuses))        # 3 — how many in total
print("PAID" in statuses)   # True — is it there at all

statuses.append("DELIVERED")   # added to the end
print(statuses)
Run

Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →

Asking for an element that does not exist crashes the program with IndexError: list index out of range. A useful error: it means fewer values arrived than you expected.

Practice here: Build a list · Take an element by index · Append to a list

A dict: a value by name

When every value has its own name, an index does not help — you need a dict. Curly braces, key: value pairs, access by key:

live example

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

print(order["status"])           # PAID — taken by field name

order["status"] = "SHIPPED"      # changed a value
order["paid_at"] = "2026-09-12"  # added a field that was not there
print(order)
Run

Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →

This is exactly what JSON is. A JSON response, once parsed, becomes a dict one to one: same keys, same values. So "read a field from the response" is just response["status"], and nothing more.

Practice here: Read a value by key

A field that may be missing

This is where most checks break. Asking for a missing key is not an empty value, it is an error.

The wrong way

live example

# error: there is no "discount" field, and the key lookup crashes the program
order = {"id": "ord-042", "status": "PAID", "total": 4990}

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

Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →

The right way

live example

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

print(order.get("discount"))        # None when the field is absent
print(order.get("discount", 0))     # 0 — your own value instead of None

if "discount" in order:             # when presence itself is what matters
    print(order["discount"])
else:
    print("no discount field")
Run

Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →

The difference in one sentence: square brackets demand that the field be there, get() allows it to be missing. The flip side is that get() hides the loss: if a field must always arrive, check it with in, otherwise the check stays silent about a real breakage.

Practice here: Check whether a field exists · A field that may be missing

Nesting: dicts inside lists inside dicts

A real response is a dict with other dicts and lists inside it. You read it step by step, left to right: every bracket is one step deeper.

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,
}

print(response["customer"]["name"])      # Anna
print(response["items"][0]["product"])   # Grinder — the first item
print(len(response["items"]))            # 2 — how many items

customer = response["customer"]          # lost in the chain — split it up
print(customer["name"])
Run

Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →

One long chain is shorter, but when it breaks, the message does not say at which step. Split into variables, it says so right away.

Practice here: Parse a nested response

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). In a service response they almost always come together: the response itself is a dict, and items inside it is a list of dicts.

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 crashes the program with KeyError; the safe way is get().
  • A field that must always arrive is checked with in: get() hides the loss and leaves the check green.
  • Nesting reads step by step: response["items"][0]["product"].
  • Loops — walk over every item instead of only the first.
  • Conditions — compare what you got with what you expect.
  • Functions — keep field reading in one place.
  • Working with data — count over an export and find the mismatch.