使用Python/Marshmallow验证键名可变的嵌套字典
问题描述
示例数据
{ "A-B": { "x": { "name": "x A-B" }, "y": { "level": 6 } }, "B-C": { "x": { "name": "x B-C" }, "y": { "level": 9 } }, "A-C": { "x": { "name": "x A-C" } } }
数据规则
- 顶层键为动态状态标识(如"A-B"),无固定取值范围
- 每个状态对应的对象中,可包含
x或y字段(二者均为可选),且每个字段最多出现一次 x字段必须是包含必填字符串类型name键的对象y字段必须是包含必填整数类型level键的对象
已定义基础Schema
class X_Schema(Schema): name = fields.String(required=True) class Y_Schema(Schema): level = fields.Integer(required=True)
现有方案的不足
当前实现需要为每个可能的状态单独定义字段,示例如下:
class StateSchema(Schema): a_b = fields.Dict(keys=fields.String(), values=fields.Nested(X_Schema()), required=False, data_key="A-B") b_c = fields.Dict(keys=fields.String(), values=fields.Nested(X_Schema()), required=False, data_key="B-C")
该方案需要枚举所有可能的状态字段,扩展性极差,无法适配未知状态标识。
解决方案
通过两层嵌套Schema即可实现动态顶层键的验证,无需枚举所有状态:
步骤1:定义单个状态对象的验证Schema
先处理每个状态对象内部的x和y字段验证:
class StateEntrySchema(Schema): x = fields.Nested(X_Schema(), required=False) y = fields.Nested(Y_Schema(), required=False) # 可选:如果业务要求每个状态至少包含x或y字段,添加以下验证 @validates_schema def check_at_least_one_field(self, data, **kwargs): if not data.get("x") and not data.get("y"): raise ValidationError("状态对象至少需要包含x或y字段")
步骤2:定义顶层动态Schema
使用fields.Dict接收任意顶层状态键,值嵌套上述StateEntrySchema:
class StateSchema(Schema): __root__ = fields.Dict( # 可选:如果需要验证状态键格式(如必须包含"-"),添加validate规则 keys=fields.String(validate=lambda key: "-" in key), values=fields.Nested(StateEntrySchema()) )
验证示例
# 验证合法数据 data = { "A-B": {"x": {"name": "test"}}, "C-D": {"y": {"level": 5}} } schema = StateSchema() result = schema.load(data) # 输出:{'__root__': {'A-B': {'x': {'name': 'test'}}, 'C-D': {'y': {'level': 5}}}} # 验证非法数据(x缺少name) invalid_data = {"A-B": {"x": {}}} errors = schema.validate(invalid_data) # 输出:{'__root__': {'A-B': {'x': {'name': ['Missing data for required field.']}}}}
该方案的优势:
- 自动适配任意动态状态键,无需枚举
- 严格验证
x和y字段的格式要求 - 保证每个状态对象内
x和y各自最多出现一次
内容的提问来源于stack exchange,提问作者ScaryAardvark
相关产品推荐
相关产品推荐

