使用anyOf关键字验证jsonschema时设置默认值

2024-09-25 00:32:29 发布

您现在位置:Python中文网/ 问答频道 /正文

我想使用模式中定义的默认值。我发现python jsonschema faq中已经有一个示例: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}]}

我不明白为什么。我猜字典是在通过验证器时更改的,因为它们是可变的。因此,在全局上下文中,尝试重置实例可能不会产生预期效果。然而,我不知道如何解决这个问题。有人能帮忙吗


Tags: instancenameindefaultschematypeanotherproperty