phillipdupuis / pydantic-to-typescript

CLI Tool for converting pydantic models into typescript definitions
MIT License
285 stars 48 forks source link

Enums used as `Dict` keys are lost when converted to TS #17

Closed talyh closed 2 years ago

talyh commented 2 years ago

Hello. Thanks for this package, it makes our lives a lot easier in keeping definitions consistent across BE and FE repos.

One challenge we've been been facing is with loss of information when it comes to restricted keys in dictionaries.

For instance

example_attribute: Dict[ProvinceEnum,str]

becomes

example_attribute: {
    [k: string]: string;
  };

which means any string is a valid key, not just the ones defined within the enum.

Is there a different Pydantic representation that would be better suited to produce a more restrictive TS version? Or is this a gap in the code generation?

phillipdupuis commented 2 years ago

Hi @talyh, good question!

I think we can make that happen if we do a bit of trickery with TypedDict. It lets you define a restricted set of keys, and if you pass the total=False parameter in the constructor it considers any subset of those keys to be valid. (As opposed to requiring that all keys be present). https://docs.python.org/3/library/typing.html#typing.TypedDict

For example, I just tried out this little enum_map helper to dynamically generate restricted mapping types. If you have the following models:

class ProvinceEnum(str, Enum):
    foo = "foo"
    bar = "bar"

def enum_map(keys: Type[Enum], value_type: type) -> Type[TypedDict]:
    name = f"Map{keys.__name__}To{value_type.__name__.title()}"
    return TypedDict(name, {x.value: value_type for x in keys}, total=False)

class Example(BaseModel):
    example_attribute: enum_map(ProvinceEnum, str)

These are the definitions generated by pydantic-to-typescript:

export interface Example {
  example_attribute: MapProvinceEnumToStr;
}
export interface MapProvinceEnumToStr {
  foo?: string;
  bar?: string;
}

Hopefully that helps!

talyh commented 2 years ago

Hello Thanks for the tip. We'll give it a shot with our own enums