msgspec icon indicating copy to clipboard operation
msgspec copied to clipboard

Nested tagged unions?

Open uar316025 opened this issue 1 year ago • 1 comments

Question

Hi! Can i do something like that? (not sure how to name it) Here example with pydantiic (v2)

First tagged union by type field Then second by sub_type field

from typing import Literal, Annotated

import pydantic

class Device(pydantic.BaseModel):
    type: int
    sub_type: int
    name: str

class BlenderM1(Device):
    type: Literal[0x01] = 0x01
    sub_type: Literal[0x01] = 0x01

class BlenderM2(BlenderM1):
    sub_type: Literal[0x02] = 0x02

class OvenM1(Device):
    type: Literal[0x02] = 0x02
    sub_type: Literal[0x01] = 0x01

class OvenM2(OvenM1):
    sub_type: Literal[0x02] = 0x02

AnyDevice = Annotated[
    Annotated[BlenderM1 | BlenderM2, pydantic.Field(discriminator='sub_type')] |
    Annotated[OvenM1 | OvenM2, pydantic.Field(discriminator='sub_type')],
    pydantic.Field(discriminator='type')
]
print(pydantic.TypeAdapter(list[AnyDevice]).validate_python([
    {'type': 0x01, 'sub_type': 0x01, 'name': 'BM1'},
    {'type': 0x01, 'sub_type': 0x02, 'name': 'BM2'},
    {'type': 0x02, 'sub_type': 0x01, 'name': 'OM1'},
    {'type': 0x02, 'sub_type': 0x02, 'name': 'OM2'},
]))

which prints

[
  BlenderM1(type=1, sub_type=1, name='BM1'),
  BlenderM2(type=1, sub_type=2, name='BM2'),
  OvenM1(type=2, sub_type=1, name='OM1'),
  OvenM2(type=2, sub_type=2, name='OM2')
]

Also i can't change the devices structure because it is defined externally.

uar316025 avatar Jul 10 '24 09:07 uar316025

yeah.. pydantic gets this one right I think - in that the tag field is a property of the union, not of each struct. With that property, this is straightforward to represent. OpenAPI and an upcoming revision of JSONSchema also have this as a property of the union.

Msgspec's decision to have the tag field be a property of each struct instead might make this unreasonably difficult. Maybe that's impetus to re-examine https://github.com/jcrist/msgspec/issues/338 ? Putting this on the union instead (with an Annotated like pydantic, or by some other method, possibly even automatic?) could maybe open the door to allowing unions of TypedDicts, dataclasses, etc as well.

jwhitaker-gridcog avatar Jan 11 '25 12:01 jwhitaker-gridcog