Walking it
for person in people:
for lang in person["langs"]:
The outer loop takes records, the inner takes the list inside each one. That is the shape of most processing you will do, and the flatten version of it is a nested comprehension:
[lang for person in people for lang in person["langs"]]
Same clause order as the loops, and worth using only when it fits on one line.
The missing-key problem
data["user"]["settings"]["theme"]
raises as soon as any level is absent, and the KeyError names only the key that failed, not the path you were walking. Two ways to be safe:
data.get("user", {}).get("settings", {}).get("theme", "default")
Each get returns {} rather than None when absent, so the next get still has a dictionary to call. That {} default is the trick that makes the chain work.
For anything deeper than two or three levels, a small helper is clearer than a long chain, and a try/except KeyError around the whole path is clearer still when a missing value really is exceptional.
Building nested shapes
Flat rows into groups is the most common transformation:
teams.setdefault(team, []).append(name)
and summing across two levels is a single generator expression:
sum(item["price"] for o in orders for item in o["items"])
Printing it
Nested structures print as one dense line. json.dumps(data, indent=2) renders them readably and is the fastest way to see the shape of something you have just parsed. It only works for JSON-compatible types, which covers most data that arrived as JSON in the first place.