cd /news/developer-tools/a-dictionary-check-does-not-validate… · home › topics › developer-tools › article
[ARTICLE · art-157579] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=· neutral

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

A type-contract review of a Python reply extractor shows that checking whether each content part is a dictionary does not validate the type of its text field, so a JSON value like {"text": null} passes the isinstance check but raises a TypeError when passed to str.join. The analysis notes that the list-normalization expression sits outside an earlier try block that catches lookup errors, meaning a join failure and a lookup failure take different paths, and recommends defining the supported input shapes and expected behavior for unsupported ones rather than treating one isinstance check as full schema validation.

by read3 min views1 publishedOct 26, 2026

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 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.

── more in #developer-tools 4 stories · sorted by recency
── more on @python 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/a-dictionary-check-d…] indexed:0 read:3min 2026-10-26 · —