我想使用在我的架构中定义的默认值。我发现 python-jsonschema 常见问题解答中已经有一个示例:https ://python-jsonschema.readthedocs.io/en/stable/faq/
该示例扩展了property
关键字的默认验证器并根据需要设置默认值。anyOf
但是,一旦在同一架构中使用关键字,我就会遇到问题。
让我给你举个例子:
from jsonschema import Draft7Validator, validators
def extend_with_default(validator_class):
validate_properties = validator_class.VALIDATORS["properties"]
def set_defaults(validator, properties, instance, schema):
for property, subschema in properties.items():
if "default" in subschema:
instance.setdefault(property, subschema["default"])
for error in validate_properties(
validator, properties, instance, schema,
):
yield error
return validators.extend(
validator_class, {"properties": set_defaults},
)
DefaultValidatingDraft7Validator = extend_with_default(Draft7Validator)
obj = {
"my_list": [{"class_name": "some_class"}]
}
schema = {
"properties": {
"my_list": {
"type": "array",
"items": {
"anyOf": [
{
"type": "object",
"properties": {
"class_name": {
"const": "some_class"
},
"some_property": {
"type": "number",
"default": 1
}
},
"required": ["class_name", "some_property"],
"additionalProperties": False
},
{
"type": "object",
"properties": {
"class_name": {
"const": "another_class"
},
"another_property": {
"type": "number",
"default": 1
}
},
"required": ["class_name", "another_property"],
"additionalProperties": False
}
]
}
}
}
}
DefaultValidatingDraft7Validator(schema).validate(obj)
print(obj)
此示例实际上按预期工作。运行它提供以下输出:
{'my_list': [{'class_name': 'some_class', 'some_property': 1}]}
因此,该属性some_property
已正确设置为默认值 1。但是,如果我们现在class_name
将对象内的 更改为another_class
,这适合anyOf
列表中的第二个条目,我们会遇到以下问题:
obj = {
"my_list": [{"class_name": "another_class"}]
}
=>
jsonschema.exceptions.ValidationError: {'class_name': 'another_class', 'some_property': 1, 'another_property': 1} is not valid under any of the given schemas
Failed validating 'anyOf' in schema['properties']['my_list']['items']:
{
"anyOf": [
{
"type": "object",
"properties": {
"class_name": {
"const": "some_class"
},
"some_property": {
"type": "number",
"default": 1
}
},
"required": ["class_name", "some_property"],
"additionalProperties": False
},
{
"type": "object",
"properties": {
"class_name": {
"const": "another_class"
},
"another_property": {
"type": "number",
"default": 1
}
},
"required": ["class_name", "another_property"],
"additionalProperties": False
}
]
}
On instance['my_list'][0]:
{'another_property': 1,
'class_name': 'another_class',
'some_property': 1}
在迭代anyOf
列表时,给定的实例anyOf
已经被第一个子模式更改。验证器调用每个子模式的anyOf
所有相关验证器,结果第一个子模式的properties
验证器将第一个子模式的默认值插入到当前实例中。当子模式的验证不成功时,也会发生这种情况anyOf
。结果,不适合此示例的第一个子模式插入了属性'some_property': 1
:
obj = {'class_name': 'another_class', 'some_property': 1, 'another_property': 1}
所以现在anyOf
到了通常适合对象的第二个子模式,另一个键已添加到实例中,导致验证失败以及additionalProperties
不允许。结果,在其中没有找到有效的模式anyOf
,我们得到了上述错误。
那么如何解决这个问题呢?我的方法是anyOf
在迭代子模式列表时存储实例的值。如果子模式不匹配,则应还原此子模式所做的所有更改。不幸的是,直到现在,我都无法实现这种行为。
作为参考,这是我最近尝试的样子:
def extend_with_default(validator_class):
validate_properties = validator_class.VALIDATORS["properties"]
def set_defaults(validator, properties, instance, schema):
for property, subschema in properties.items():
if "default" in subschema:
instance.setdefault(property, subschema["default"])
for error in validate_properties(
validator, properties, instance, schema,
):
yield error
def any_of(validator, subschemas, instance, schema):
instance_copy = instance.copy()
all_errors = []
for index, subschema in enumerate(subschemas):
errs = list(validator.descend(instance, subschema, schema_path=index))
if not errs:
break
instance = instance_copy # Make sure an instance that did not fit is not modified
all_errors.extend(errs)
else:
yield ValidationError(
"%r is not valid under any of the given schemas" % (instance,),
context=all_errors,
)
return validators.extend(
validator_class, {"properties": set_defaults, "anyOf": any_of},
)
在内部这似乎有效,验证也有效。但由于某种原因,现在obj
给出的内容{"my_list": [{"class_name": "another_class"}]}
是:
{'my_list': [{'class_name': 'another_class', 'some_property': 1}]}
我不明白为什么。我猜字典在通过验证器时会发生变化,因为它们是可变的。因此,尝试重置实例可能不会在全局上下文中产生预期的效果。但是,我无法弄清楚如何解决这个问题。有人可以帮忙吗?