Skip to content

fix: detect anyOf null members by type, not exact dict equality#2617

Open
nyxst4ck wants to merge 1 commit into
googleapis:mainfrom
nyxst4ck:fix/anyof-null-member-with-extra-keys
Open

fix: detect anyOf null members by type, not exact dict equality#2617
nyxst4ck wants to merge 1 commit into
googleapis:mainfrom
nyxst4ck:fix/anyof-null-member-with-extra-keys

Conversation

@nyxst4ck

Copy link
Copy Markdown

Problem

handle_null_fields removes the null member of an anyOf schema with
schema['anyOf'].remove({'type': 'null'}) — i.e. by exact dict equality.
JSON Schemas frequently annotate the null member with extra keys, e.g.
{'type': 'null', 'title': 'N'} or {'type': 'null', 'description': '...'}
(common output from MCP tools and other JSON-Schema sources). In that case no
element equals the bare {'type': 'null'}, so:

ValueError: list.remove(x): x not in list

This is reachable from the public path: t_schemaprocess_schema
handle_null_fields, hit when a dict response_schema is passed to
generate_content. It also mutates the list while iterating it. Plain pydantic
emits a bare {'type': 'null'}, which is why existing tests never exercised it.

Fix

Identify null members by item.get('type') == 'null' (with an isinstance
guard) and rebuild the anyOf list instead of remove()-ing by equality. This
preserves the existing flatten-when-one-type-remains behavior, no longer mutates
during iteration, and tolerates null members with extra keys (and multiple null
members).

Testing

Added test_handle_null_fields_anyof_null_member_with_extra_keys. It raises
ValueError on main and passes with the fix. The full
tests/transformers/test_schema.py suite passes (54 passed).

handle_null_fields removed the null member of an anyOf with
schema['anyOf'].remove({'type': 'null'}), which matches by exact dict equality.
A null member annotated with extra keys (e.g. {'type': 'null', 'title': 'N'},
common in JSON Schemas from MCP tools) doesn't equal the bare {'type': 'null'},
raising ValueError: list.remove(x): x not in list. It also mutated the list
while iterating.

Detect null members by item.get('type') == 'null' and rebuild the anyOf list
instead, preserving the flatten-when-single-type behavior.
@Venkaiahbabuneelam Venkaiahbabuneelam self-assigned this Jun 17, 2026
@Venkaiahbabuneelam Venkaiahbabuneelam added the size:L Code changes between 40-100 lines label Jun 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L Code changes between 40-100 lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants