mac01021
mac01021

Reputation: 825

How to make a custom serde schema in pydantic v2 without custom validation?

I am trying to use Pydantic v2 to generate JSON schemas for all my domain classes and to marshal my domain objects to and from JSON.

Suppose I have a class


class Component:

    def __init__(self, component_id: int):
        self.component_id = component_id

    @property
    def unique_id(self) -> str:
        return f"{type(self).__name__}-{self.component_id}"

    @classmethod
    def parse(unique_id: str):
        return Component(int(unique_id.split("-", 1)[1]))

and a BaseModel

class Machine(BaseModel):

    part_1: Component
    part_2: Component
    part_3: Component

The basic behavior I want is to be able to perform the following operations:


# create a machine
my_machine = Machine(
    part_1=Component(1),
    part_2=Component(2),
    part_3=Component(3),
)

# serialize to json
serialized_machine = my_machine.model_dump_json()

# deserialize to get my machine again
copy_of_my_machine = Machine.model_validate_json(serialized_machine)

# and, lastly, get the json schema for machines
schema_json = Machine.json.dumps(Machine.model_json_schema(), indent=2)

Importantly, though, when I serialize the components within the machine, I want to serialize them as strings using their unique id method.

If I add to Component this schema-customization method:

@classmethod
    def __get_pydantic_core_schema__(
            cls, source: Type[Any], handler: GetCoreSchemaHandler
    ) -> core_schema.CoreSchema:
        return core_schema.str_schema()

then Machine.model_json_schema() now returns the desired schema (with all the properties being typed as strings). But, having done this, I lose the ability to even construct my machine the way I could before:

Machine(
    part_1=Component(1),
    part_2=Component(2),
    part_3=Component(3),
)

## => raises:
##
## pydantic_core._pydantic_core.ValidationError: 3 validation errors for Machine
## part_1
##  Input should be a valid string [type=string_type, input_value=<__main__.Component object at 0x103315f10>, input_type=Component]
##    For further information visit https://errors.pydantic.dev/2.1/v/string_type
## part_2
##   Input should be a valid string [type=string_type, input_value=<__main__.Component object at 0x103315ee0>, input_type=Component]
##    For further information visit https://errors.pydantic.dev/2.1/v/string_type
## part_3
##   Input should be a valid string [type=string_type, input_value=<__main__.Component object at 0x103315130>, input_type=Component]
##     For further information visit https://errors.pydantic.dev/2.1/v/string_type

In summary, how do I get what I want?

Upvotes: 1

Views: 1112

Answers (1)

mac01021
mac01021

Reputation: 825

I figured out, based on another page in a different part of the docs, that I can, inside of Component,

@classmethod
    def __get_pydantic_core_schema__(
            cls, source: Type[Any], handler: GetCoreSchemaHandler
    ) -> core_schema.CoreSchema:
        assert issubclass(source, Component)
        return core_schema.json_or_python_schema(
            json_schema=core_schema.str_schema(),
            python_schema=core_schema.is_instance_schema(Component),
            serialization=core_schema.plain_serializer_function_ser_schema(lambda c: c.unique_id)
        )

This defines python validation logic separately from json validation logic.

Upvotes: 2

Related Questions