Skip to content

[python] fix null handling in generated models#24357

Open
fa0311 wants to merge 4 commits into
OpenAPITools:masterfrom
fa0311:fix/python-null-value-serialization
Open

[python] fix null handling in generated models#24357
fa0311 wants to merge 4 commits into
OpenAPITools:masterfrom
fa0311:fix/python-null-value-serialization

Conversation

@fa0311

@fa0311 fa0311 commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Fixes a few ways the python generator mishandles null when (de)serializing models.

to_dict() drops null entries from containers

to_dict() rebuilds containers of models by hand and skipped falsy entries, so None items disappeared:

Container(items=[item, None]).to_dict()          # -> {'items': [{...}]}          length changed!
Container(mapping={'a': None, 'b': item}).to_dict()  # -> {'mapping': {'b': {...}}}   key 'a' gone

A dropped element shifts every following index for the consumer, and a dropped map key is just lost. Now the None is kept as an explicit null. The list-of-list / list-of-dict from_dict comprehensions also crashed on a null inner container ('NoneType' object has no attribute ...); those are guarded too.

from_dict() resurrects the default over an explicit null

For a nullable property with a default, from_dict used obj.get(k) if obj.get(k) is not None else <default>, so an explicit JSON null was indistinguishable from an absent key and got replaced by the default:

# property: {type: string, nullable: true, default: "hello"}
Container.from_dict({"note": None}).note   # -> "hello"   (should be None)

This round-trips as silent data corruption (from_json(x.to_json()) turns an intentional None back into the default). Changed to check key presence for nullable properties; non-nullable properties keep the old behavior.

get_discriminator_value() raises KeyError instead of ValueError

When the discriminator property is missing from the payload, obj[cls.__discriminator_property_name] raised a bare KeyError, while the code a few lines below builds a descriptive ValueError for unmapped values. The rest of the deserialization stack catches ValueError, so the KeyError leaked out. Switched to obj.get().

Verified list/map null preservation, nullable-default round-trips, and the discriminator path against generated clients; petstore sample tests still pass.

PR checklist

  • Read the contribution guidelines.
  • Ran ./mvnw clean package and regenerated the python samples.
  • Filed the PR against master.

/cc @cbornet @tomplus @krjakbrjak


Summary by cubic

Fix incorrect null handling in generated python models to preserve data shape during (de)serialization and align error behavior with the rest of the stack. Keeps explicit None values and avoids crashes with null inner containers.

  • Bug Fixes
    • Preserve None entries in lists and dict values in to_dict(); use single conditional expressions for container items to keep mypy happy.
    • For nullable properties with defaults, from_dict() checks key presence so an explicit null isn’t replaced by the default; non-nullable behavior unchanged.
    • Guard from_dict() list-of-list and list-of-dict comprehensions so null inner containers don’t crash.
    • Use obj.get() in discriminator lookup so a missing discriminator raises a descriptive ValueError instead of a KeyError.

Written for commit 4040d87. Summary will update on new commits.

Review in cubic

fa0311 added 4 commits July 20, 2026 06:49
to_dict() rebuilt containers of models with truthiness checks, so None
entries were dropped from lists (changing their length) and from dict
values. Keep them as explicit nulls instead, and guard the list-of-list /
list-of-dict from_dict comprehensions so a null inner container no longer
crashes.

from_dict() also used 'obj.get(k) is not None' when applying property
defaults, which silently replaced an explicit JSON null with the schema
default on nullable properties. Check key presence instead (nullable
properties only, non-nullable keep the old behavior).

get_discriminator_value() now uses obj.get() so a payload without the
discriminator property raises the descriptive ValueError instead of a
bare KeyError.
The previous commit added an else-branch that assigned None to the
per-key accumulators (e.g. _field_dict_of_array[k] = None). mypy fixes the
dict value type from the first non-None assignment, so the None branch
tripped 'Incompatible types in assignment' on the legacy-model-dictionaries
sample. Fold each container branch into a single conditional expression so
the accumulator is inferred as Optional from the start. Same output,
same null-preserving behaviour.
@fa0311
fa0311 force-pushed the fix/python-null-value-serialization branch from 569ec4a to 4040d87 Compare July 20, 2026 07:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant