{"slug": "a-dictionary-check-does-not-validate-the-type-of-its-text-field", "title": "A Dictionary Check Does Not Validate the Type of Its Text Field", "summary": "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.", "body_md": "*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.*\n\nA 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.\n\nThe extractor examined here contains this expression:\n\n```\ncontent = \"\\n\".join(\n    p.get(\"text\", \"\") for p in content if isinstance(p, dict)\n)\n```\n\nThe 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.\n\nThe 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.\n\nPython'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.\n\n| Fictional content value | Source-derived behavior | \n|---|---|\n| `[{\"text\": \"First\"}, {\"text\": \"Second\"}]` | Join the two strings with a newline | \n| `[{\"other\": \"ignored\"}, {\"text\": \"Visible\"}]` | Supply an empty first part; the final outer strip removes the leading newline | \n| `[\"plain member\", {\"text\": \"Visible\"}]` | Skip the non-dictionary member and retain the dictionary's text | \n| `[{\"text\": null}]` | Supply a non-string to the join operation | \n| `[]` | Produce an empty string, then reach the empty-content rejection | \n\nThe 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.\n\nThe 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.\n\nThere 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.\n\nThis 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.\n\nA 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.\n\nFor 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.", "url": "https://wpnews.pro/news/a-dictionary-check-does-not-validate-the-type-of-its-text-field", "canonical_source": "https://dev.to/tokenpeakai/a-dictionary-check-does-not-validate-the-type-of-its-text-field-3a7f", "published_at": "2026-10-26 02:26:49+00:00", "updated_at": "2026-10-26 02:42:55.769791+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["Python"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/a-dictionary-check-does-not-validate-the-type-of-its-text-field", "markdown": "https://wpnews.pro/news/a-dictionary-check-does-not-validate-the-type-of-its-text-field.md", "text": "https://wpnews.pro/news/a-dictionary-check-does-not-validate-the-type-of-its-text-field.txt", "jsonld": "https://wpnews.pro/news/a-dictionary-check-does-not-validate-the-type-of-its-text-field.jsonld"}}