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 isget(). - 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"].
What to read next
- 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.