LESSON 122 · DEEPER PYTHON30 min

Read Nested JSON with Explicit Missing-Field Rules

Extract a city from nested JSON data while distinguishing a missing optional value from an invalid object shape.

Example check: Worked and guided code samples are checked against CPython 3.13. Practice uses Pyodide 0.29.5 (Python 3.13) in your browser. How we check lessons

Video lesson: Read Nested JSON with Explicit Missing-Field Rules

The preview is stored on this site. YouTube loads only when you press Play. Watch on YouTube

Read the video transcript

Select a timestamp to watch that moment on YouTube.

  1. 00:00

    A JSON document can contain an address with a city, omit the address, or place a completely different kind of value at that path. A quick nested-index expression works on one perfect sample and often fails on the first real variation. Today's function has a precise rule: missing optional address or city means no location and returns None; a present value with the wrong shape is an error. Before writing code, say those cases out loud so the fix does not collapse them into one silent fallback. JSON can encode nested objects, arrays, and null values, but json.loads only parses syntax.

  2. 00:41

    It does not promise that a field exists or has the expected Python type. An API may return a user object with an address and city, no address at all, or an address field that is a list by mistake. A safe extractor distinguishes the allowed missing case from an invalid shape. The contract in this lesson returns None when address or city is absent, but raises ValueError when a present container or city value has the wrong type. The broken run raises KeyError for address. If the key existed with a JSON null, Python would see None and the next subscript would fail in another way. A blanket except around every possible error would hide which assumption was

  3. 01:23

    violated. Instead, inspect the path one level at a time. The first question is whether the root is a dictionary. The second is whether user is a dictionary. Only then should we ask whether address is absent or present. The broken expression record['user']['address']['city'] works only on the happy path. Missing keys raise KeyError; a null address produces TypeError; a list instead of a dictionary can fail differently. Chaining get calls blindly does not solve all of this because get on a non- dictionary still fails and can hide unexpected input. Print the types at each boundary and define what the product considers

  4. 02:04

    optional. Here the root and user must be objects, while address and city may be absent. The diagnosis prints NoneType for one sample and list for another. Both are valid Python values, and both can come from syntactically valid JSON, but they mean different things under our contract. None is allowed here as optional absence. A list is not a valid address object. Calling get blindly on a list would raise an accidental attribute error, while returning None for that list would conceal broken upstream data. This is why parsing syntax and validating shape should be separate steps.

  5. 02:45

    Start with a helper that checks isinstance(record, dict) and isinstance(user, dict). Read address with get. If it is None because the field is missing or explicitly null, return None according to the stated policy. If it is present but not a dictionary, raise ValueError. Read city similarly; if present it must be a nonempty string. This nested validation is ordinary Python and works after json.loads or with an already parsed dictionary. Do not silently convert numbers to city names or treat malformed objects as missing. The corrected function checks each container before going deeper.

  6. 03:26

    It uses get only after verifying it has a dictionary. It returns None for optional missing values, but raises ValueError for a present address that is not an object or a present city that is not nonempty text. We do not call str on a numeric city because that would invent a location name from invalid data. This can be used after json.loads or with an already parsed object in the browser exercise. Validation order matters. If a root is a list, trying record.get would itself fail, so inspect root before using dictionary methods. If user is absent, this example treats that as a malformed required field rather

  7. 04:07

    than an optional location. Address and city are optional; a present wrong type is malformed. Error messages should identify the field that failed, but avoid placing sensitive user data inside them. The browser checks verify both the allowed missing path and the rejected malformed path, so a solution that merely chains get calls cannot pass. A common shortcut uses chained get calls with an empty dictionary as every fallback. That can return None for a malformed list, accidentally presenting bad data as an ordinary missing value. The explicit branches are longer, yet each branch corresponds to a sentence in the

  8. 04:47

    contract. If a future API schema changes, you can update one branch and its test. In production, you might use a schema validation library, but this small exercise builds the reasoning that such libraries encode. Keep the difference between absence and invalid type in the final explanation and ask which one should alert the caller. The final run proves that a missing address returns None and a list address is rejected with a clear message. Add a null city, an integer city, an empty string, and a valid name. Each test should line up with the contract written above the function.

  9. 05:27

    Complete the site's checked task and quiz, then compare this explicit approach with the direct indexing expression from the opening. The difference is not only fewer exceptions; it is a more informative model of the data producer's promises and failures. The checked task takes a parsed Python object so the browser console can focus on traversal. A solved example also calls json.loads to show the parsing boundary. Test valid city text, missing address, missing city, null address, a list in place of address, and an integer city. The return value None has a documented meaning: optional location unavailable.

  10. 06:08

    The exception means the producer violated the expected shape. Keeping those states distinct makes later error reporting and debugging much clearer.

Understand the concept

JSON can encode nested objects, arrays, and null values, but json.loads only parses syntax. It does not promise that a field exists or has the expected Python type. An API may return a user object with an address and city, no address at all, or an address field that is a list by mistake. A safe extractor distinguishes the allowed missing case from an invalid shape. The contract in this lesson returns None when address or city is absent, but raises ValueError when a present container or city value has the wrong type.

The broken expression record['user']['address']['city'] works only on the happy path. Missing keys raise KeyError; a null address produces TypeError; a list instead of a dictionary can fail differently. Chaining get calls blindly does not solve all of this because get on a non-dictionary still fails and can hide unexpected input. Print the types at each boundary and define what the product considers optional. Here the root and user must be objects, while address and city may be absent.

Start with a helper that checks isinstance(record, dict) and isinstance(user, dict). Read address with get. If it is None because the field is missing or explicitly null, return None according to the stated policy. If it is present but not a dictionary, raise ValueError. Read city similarly; if present it must be a nonempty string. This nested validation is ordinary Python and works after json.loads or with an already parsed dictionary. Do not silently convert numbers to city names or treat malformed objects as missing.

The checked task takes a parsed Python object so the browser console can focus on traversal. A solved example also calls json.loads to show the parsing boundary. Test valid city text, missing address, missing city, null address, a list in place of address, and an integer city. The return value None has a documented meaning: optional location unavailable. The exception means the producer violated the expected shape. Keeping those states distinct makes later error reporting and debugging much clearer.

  • JSON parsing is not schema validation
  • Optional and malformed values are different states
  • Check each nested container before using it
  • Return None only for an explicitly allowed absence

See it step by step

Read the code, predict the output, then compare it with the result.

01. Parse a nested JSON object

PYTHON
import json
record = json.loads('{"user": {"address": {"city": "Bratislava"}}}')
print(record['user']['address']['city'])
EXPECTED OUTPUT
Bratislava

Parsing creates dictionaries, but the indexing succeeds only because this specific sample has the expected shape.

02. See a missing optional field

PYTHON
record = {'user': {}}
print(record['user'].get('address'))
EXPECTED OUTPUT
None

An absent address yields None under the lesson's optional-field policy.

03. Recognize malformed present data

PYTHON
record = {'user': {'address': ['Oslo']}}
print(isinstance(record['user']['address'], dict))
EXPECTED OUTPUT
False

A list is present but is not the required object shape.

A closer look

Follow the reasoning, inspect each result, then try the suggested changes in the console below.

01 / 03

Separate parsing from schema

JSON can encode nested objects, arrays, and null values, but json.loads only parses syntax. It does not promise that a field exists or has the expected Python type. An API may return a user object with an address and city, no address at all, or an address field that is a list by mistake. A safe extractor distinguishes the allowed missing case from an invalid shape. The contract in this lesson returns None when address or city is absent, but raises ValueError when a present container or city value has the wrong type.

Change city to a number and note that json.loads still succeeds. Syntax validity is only the first step. A producer and consumer need a shared data contract for fields and types.

PYTHON
import json
record = json.loads('{"user": {"address": {"city": "Bratislava"}}}')
print(record['user']['address']['city'])
EXPECTED OUTPUT
Bratislava
Follow the reasoning
  1. Parse a valid JSON sample.
  2. Identify the assumed nested fields.
02 / 03

Define optional absence

The broken expression record['user']['address']['city'] works only on the happy path. Missing keys raise KeyError; a null address produces TypeError; a list instead of a dictionary can fail differently. Chaining get calls blindly does not solve all of this because get on a non-dictionary still fails and can hide unexpected input. Print the types at each boundary and define what the product considers optional. Here the root and user must be objects, while address and city may be absent.

A missing address and a JSON null value become None in Python. This lesson accepts both as optional absence. A different product may distinguish them, but that choice must be stated explicitly.

PYTHON
record = {'user': {}}
print(record['user'].get('address'))
EXPECTED OUTPUT
None
Follow the reasoning
  1. Use get for an optional field.
  2. Decide what None means.
03 / 03

Reject present invalid shapes

Start with a helper that checks isinstance(record, dict) and isinstance(user, dict). Read address with get. If it is None because the field is missing or explicitly null, return None according to the stated policy. If it is present but not a dictionary, raise ValueError. Read city similarly; if present it must be a nonempty string. This nested validation is ordinary Python and works after json.loads or with an already parsed dictionary. Do not silently convert numbers to city names or treat malformed objects as missing.

Check the type before using dictionary methods. A list can be valid JSON yet invalid at this particular path. Return a clear ValueError instead of allowing an accidental attribute or key exception.

PYTHON
record = {'user': {'address': ['Oslo']}}
print(isinstance(record['user']['address'], dict))
EXPECTED OUTPUT
False
Follow the reasoning
  1. Check a present address is a dictionary.
  2. Reject a malformed city value.

Try it in Python

Edit the example and run it. Python starts in your browser the first time you click Run.

Python console

Ready to run
Need input()? Add one value per line
Ctrl / ⌘ + Enter to run
OUTPUT
Your output appears here.
Your turnImplement user_city(record). Root and user must be dictionaries. Missing or null address and missing or null city return None. A present address of another type, or a present city that is not a nonempty string, raises ValueError. The prepared call must print Bratislava.
Need a hint?

Validate root and user with isinstance(..., dict). Read address = user.get('address'); return None if address is None, otherwise require dict. Apply the same optional rule to city, then require a nonempty str.

Complete the task and select Check task to verify your code.

    Quick quiz

    Three questions. You can change your answers and try again.

    01.Does json.loads verify a required city field exists?

    02.What should this lesson return when address is absent?

    03.Why reject address=[] instead of returning None?

    Typical mistakes

    Everyone meets these errors. See what causes them and how to fix them.

    Indexing every field directly

    COMMON MISTAKE
    record['user']['address']['city']
    FIX
    address = user.get('address')
    if address is None:
        return None

    What happens: Missing keys or null values raise unrelated KeyError or TypeError exceptions.
    Handle optional absence before deeper traversal.

    Chaining get without checking types

    COMMON MISTAKE
    record.get('user', {}).get('address', {}).get('city')
    FIX
    if not isinstance(address, dict):
        raise ValueError('address must be an object')

    What happens: A present list or null can still raise AttributeError or mask a malformed shape.
    Verify each present container's type explicitly.

    Browse this lesson’s problem fixes

    Sources and further reading