Skip to content

Conversation

@alythobani
Copy link

@alythobani alythobani commented Mar 20, 2025

Summary

Fixes: #28

Adds an --all-fields-required option (defaults to False) that ensures no generated TypeScript interface fields are marked as optional (fieldName?: ...), even if they have default values or default factories in the Pydantic models.

Motivation

Fields with defaults are viewed as optional by Pydantic, and thus currently become optional in the generated TypeScript interfaces.

This makes sense for API request schemas: clients don't need to provide values for these fields, since Pydantic can populate them with their defaults. However, for response schemas, the TS client should be able to know that these fields will be present, since Pydantic will populate them before sending the response data.

So, this new option (--all-fields-required) allows devs to represent response schemas without unnecessary optional markers (?) being added to fields that will always be present.

Approach

The implementation is nice and simple: at the end of each _clean_json_schema call, if --all-fields-required is set, we ensure every property name in schema["properties"] is included in schema["required"].

Comparison to existing PR #31

An existing PR (#31, filed by @bibermann in 2022) implements a similar flag --readonly-interfaces and is also marked to resolve #28.

It takes a slightly different approach, only operating on Pydantic V1 models (Pydantic V2 didn't exist back then), and marking a field as required if allow_none is true.

Under that approach, fields could still be marked as optional in the generated TS interfaces. E.g. under --readonly-interfaces, this:

class Foo(BaseModel):
    required: str
    default: str = "foo"
    optional: Optional[str]
    optional_default: Optional[str] = "foo"

would be generated into this:

export interface Foo {
  required: string;
  default: string;
  optional?: string;
  optional_default?: string;
}

Whereas under --all-fields-required it would be generated into this:

export interface Foo {
  required: string;
  default: string;
  optional: string | null;
  optional_default: string | null;
}

As we can see, another difference in the outputs is the existence of null types, but that's because of the pydantic2ts v2.0.0 release in Nov 2024.

Note on required fields in Pydantic V1 vs V2

Under Pydantic V1, nullable fields were implicitly given default values of None, which the Pydantic docs mention here:

Note

In Pydantic V1, fields annotated with Optional or Any would be given an implicit default of None even if no default was explicitly specified. This behavior has changed in Pydantic V2, and there are no longer any type annotations that will result in a field having an implicit default value.

But since --all-fields-required is just meant to mark every field as required whether or not it has a default or is nullable, our approach should be sound for either V1 or V2.

…uired

Previously, some Pydantic models that were only indirectly referenced
(e.g. buried inside an Annotated[Union[...]] field) weren't processed
before final schema generation. As a result, their default-valued fields
would be optional in the TypeScript output, even when
all_fields_required was set.

We now recursively walk and collect all reachable models before calling
`_clean_json_schema`, ensuring all schemas are properly processed.
…h v1 and v2 models)

- revert last 4 commits (serialization aliases and walking all models)
- just add any property names from the schema directly into `required`
  instead of working with the model fields directly and needing to
  account for aliases
- walking models no longer needed for all-fields-required to apply to
  all models used; even if `model` is None we still handle the schema
@alythobani alythobani changed the title Feat: add new --all-fields-required V2 flag (addresses #28: Field with default value becomes optional) Feat: add new --all-fields-required flag (addresses #28: Field with default value becomes optional) Apr 10, 2025
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.

Field with default value becomes optional on the TypeScript interface

1 participant