# A Dictionary Check Does Not Validate the Type of Its Text Field

> Source: <https://dev.to/tokenpeakai/a-dictionary-check-does-not-validate-the-type-of-its-text-field-3a7f>
> Published: 2026-10-26 02:26:49+00:00

*AI disclosure: An AI agent prepared this article from an existing Python reply extractor. All payloads are fictional inspection examples, not captured provider responses or executed tests. This is a type-contract review, with no product endorsement or commercial link.*

A function receives a list of content parts and wants to join their text. Checking that each part is a dictionary can look reassuring, but that check says nothing about the type of a value inside the dictionary.

The extractor examined here contains this expression:

```
content = "\n".join(
    p.get("text", "") for p in content if isinstance(p, dict)
)
```

The surrounding function uses that expression only when the top-level content is a list. It then checks whether the resulting content is a nonempty string and returns its stripped form.

The generator first skips list members that are not dictionaries. For each remaining member, it reads `text`, using an empty string when the key is absent. The distinction between an absent key and a present non-string value is the important part.

Python's [string join documentation](https://docs.python.org/3/library/stdtypes.html#str.join) requires string elements and describes a `TypeError` for non-string members. A dictionary containing `{"text": null}` in JSON becomes a dictionary whose value is `None` after decoding. That value is present; the default empty string is not substituted for it.

| Fictional content value | Source-derived behavior | 
|---|---|
| `[{"text": "First"}, {"text": "Second"}]` | Join the two strings with a newline | 
| `[{"other": "ignored"}, {"text": "Visible"}]` | Supply an empty first part; the final outer strip removes the leading newline | 
| `["plain member", {"text": "Visible"}]` | Skip the non-dictionary member and retain the dictionary's text | 
| `[{"text": null}]` | Supply a non-string to the join operation | 
| `[]` | Produce an empty string, then reach the empty-content rejection | 

The table predicts behavior from source and language semantics. It does not show real API traffic, establish that a provider sends these shapes, or claim any executed regression result.

The function checks the type of `content` after the join. That check is reached only if the join returns. It cannot handle a non-string part that already caused the join itself to fail.

There is another scope detail worth inspecting. An earlier `try` block catches failures while looking up the nested reply field, but the list-normalization expression sits outside that block. A caught lookup `TypeError` and a possible join `TypeError` therefore do not follow the same path in this implementation.

This is not an argument for blindly catching every exception. It is a reason to draw the type contract at the exact operation that needs it. The container must have the right shape, and each value consumed by `join()` must satisfy the string requirement.

A future implementation could reject unsupported values with a clear application error, or define which part types it intends to ignore. Converting every value to a string is another possible behavior, but it can turn missing or structured content into misleading visible text. These are policy choices; this article adds none of them to the existing code.

For a review, write down the supported input shapes and the expected behavior for each unsupported one. Keep those proposed cases labelled as unrun until tests actually execute. That produces a useful contract without pretending that one `isinstance()` check validates an entire response schema.
