dataclasses-avroschema中联合类型序列化反序列化结果不符问题
问题:dataclasses-avroschema联合类型反序列化后类型不匹配
使用dataclasses-avroschema对包含联合类型的数据类进行序列化/反序列化时,反序列化得到的对象类型与原对象不符。原对象是MessageTypeTwo,反序列化后变成了MessageTypeOne。
代码重现
from dataclasses_avroschema import AvroModel from dataclasses import dataclass import typing @dataclass class MessageTypeTwo(AvroModel): val: typing.Union[None, str] class Meta: namespace = "Messages.type.two" @dataclass class MessageTypeOne(AvroModel): class Meta: namespace = "Messages.type.one" @dataclass class CoreMessage(AvroModel): messageBody: typing.Union[ MessageTypeOne, MessageTypeTwo, ] # 序列化反序列化操作 mt2 = MessageTypeTwo(val="val") core_message = CoreMessage(messageBody=mt2) serialized = core_message.serialize() deserialized = CoreMessage.deserialize(serialized) print(deserialized.messageBody)
预期结果:MessageTypeTwo(val='val')
实际结果:MessageTypeOne()
原因分析
Avro联合类型的解析逻辑是按定义顺序尝试匹配类型。由于MessageTypeOne没有任何字段,Avro会认为任何记录都符合它的结构(Avro默认允许额外字段存在),所以反序列化时会优先匹配第一个符合条件的MessageTypeOne,而不是实际的MessageTypeTwo。
解决方案
有两种可行的解决方式:
方案1:给无字段类添加标识字段
给MessageTypeOne添加唯一字段,让Avro能区分不同类型:
@dataclass class MessageTypeOne(AvroModel): message_type: str = "type_one" # 添加唯一标识字段 class Meta: namespace = "Messages.type.one" @dataclass class MessageTypeTwo(AvroModel): message_type: str = "type_two" val: typing.Union[None, str] class Meta: namespace = "Messages.type.two"
反序列化时,Avro会根据字段值匹配对应的类型,不会再错误选择MessageTypeOne。
方案2:使用dataclasses-avroschema的鉴别器功能
通过Meta类的discriminator字段指定类型鉴别标识,序列化时自动带上类型信息,反序列化时精准匹配:
@dataclass class MessageTypeOne(AvroModel): class Meta: namespace = "Messages.type.one" discriminator = "message_type" # 指定鉴别字段名 schema_extra = {"message_type": "type_one"} # 该类型的固定标识值 @dataclass class MessageTypeTwo(AvroModel): val: typing.Union[None, str] class Meta: namespace = "Messages.type.two" discriminator = "message_type" schema_extra = {"message_type": "type_two"} @dataclass class CoreMessage(AvroModel): messageBody: typing.Union[ MessageTypeOne, MessageTypeTwo, ]
使用鉴别器后,序列化的数据会包含message_type字段,反序列化时会根据该字段的值直接匹配对应的类型,避免顺序匹配导致的错误。
验证
修改后重新运行序列化反序列化代码,输出将为MessageTypeTwo(val='val'),符合预期。
内容的提问来源于stack exchange,提问作者Oth Mane
相关产品推荐
相关产品推荐

