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

问题描述

我想利用我的架构中定义的认值。我发现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,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","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
                    }
                },"additionalProperties": False
            },{
                "type": "object","properties": {
                    "class_name": {
                        "const": "another_class"
                    },"another_property": {
                        "type": "number","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','another_property': 1}

因此,当anyOf到达通常适合该对象的第二个子模式时,另一个密钥已添加到实例,导致验证失败,并且不允许additionalProperties。结果,在anyOf中找不到有效的架构,并且出现了上述错误

那么如何解决此问题?我的方法是让anyOf在迭代子方案列表时存储实例的值。如果子模式不匹配,则应还原该子模式所做的所有更改。不幸的是,直到现在,我还无法实现这种行为。

作为参考,这是我最近的尝试:

def extend_with_default(validator_class):
    validate_properties = validator_class.VALIDATORS["properties"]

    def set_defaults(validator,):
            yield error

    def any_of(validator,subschemas,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}]}

我不明白为什么。我猜字典在通过验证器时会发生变化,因为它们是可变的。因此,尝试重置实例在全局上下文中可能无法达到预期的效果。但是,我不知道如何解决此问题。有人可以协助吗?

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)