Tomas Aschan
Tomas Aschan

Reputation: 60564

Type-checked conversion between Dataclasses and TypedDicts

I have a bunch of @dataclasses and a bunch of corresponding TypedDicts, and I want to facilitate smooth and type-checked conversion between them.

For example, consider

from dataclasses import dataclass
from typing_extensions import TypedDict

@dataclass
class Foo:
    bar: int
    baz: int
   
    @property
    def qux(self) -> int:
        return self.bar + self.baz

class SerializedFoo(TypedDict):
    bar: int
    baz: int
    qux: int

To create a serialization function, I can write something like

def serialize(foo: Foo) -> SerializedFoo:
    return SerializedFoo(
        bar=foo.bar,
        baz=foo.baz,
        qux=foo.qux,
    )

but doing this for many types becomes tedious, and every time I update the types, I also have to update the serialization function.

I can also do something like

import dataclasses

def serialize(foo: Foo) -> SerializedFoo:
    return SerializedFoo(**dataclasses.asdict(foo))

but this doesn't type check; mypy complains that it Expected keyword arguments, {...}, or dict(...) in TypedDict constructor.

Theoretically, it should be possible for a sufficiently smart type-checker to know that the dataclass has the properties needed to initialize the typed dictionary, but of course the usage of asdict makes this impossible in practice.

Is there a better way to convert a dataclass to a TypedDict with corresponding fields, that lets me both have the type checker tell me when something is wrong, and not have to type out every field in the conversion?

Upvotes: 17

Views: 6392

Answers (1)

Jeremyfx
Jeremyfx

Reputation: 61

TypedDict is a regular dict at runtime, not a real class, doesn't do any type-checking, and is only for type hinting purposes. So, you can simply use typing.cast (docs here):

import dataclasses
from typing import cast

def serialize(foo: Foo) -> SerializedFoo:
    return cast(SerializedFoo, dataclasses.asdict(foo))

which will make Python type-checkers happy.

You could also check out the dacite library. It has some nifty things for stuff like this without the overkill of marshmallow. It allows some kind of type casting in its asdict/from_dict functions.

Upvotes: 6

Related Questions