在验证期间设置JSON模式的默认值

发布于 2025-02-12 01:48:21 字数 2998 浏览 0 评论 0原文

我尝试制作一个验证器,该验证器将在验证期间从JSON模式设置默认值。

我发现了一个问题: python中的JSON模式验证器设置默认值并将其调整了一点。 由于我使用“ jsonschema == 3.2.0”,所以我想出了这样的代码:

def _with_default_setter_extension(validator_class):
    """Extend validator class with defaults setter.

    With this extension, the validator class will set all defaults from a
    schema being validated to a validated instance.
    """

    def _set_defaults(validator, properties, instance, schema):
        if not validator.is_type(instance, "object"):
            return

        valid = True
        for prop, subschema in properties.items():
            if prop in instance:
                for error in validator.descend(
                    instance[prop],
                    subschema,
                    path=prop,
                    schema_path=prop,
                ):
                    valid = False
                    yield error

        # set defaults only when validation is successful
        if valid:
            # set root default when instance is empty
            if not instance and "default" in schema:
                instance.update(schema["default"])
                return

            for prop, subschema in properties.items():
                if "default" in subschema and not isinstance(instance, list):
                    instance.setdefault(prop, subschema["default"])

    return jsonschema.validators.extend(
        validator_class, {"properties": _set_defaults}
    )

它效果很好,除了一种对我很重要的情况。我写了这样的测试以证明它对我的情况不起作用:

def test_defaults_from_oneOf_only_defaults_from_valid_schema_are_set():
    """When oneOf is used, I expect only defaults from the valid subschema to be set."""
    schema = {
        "oneOf": [
            {
                "properties": {
                    "p": {"enum": ["one"]},
                    "params": {"properties": {"q": {"default": 1}}},
                }
            },
            {
                "properties": {
                    "p": {"enum": ["two"]},
                    "params": {"properties": {"w": {"default": 2}}},
                }
            },
        ],
    }
    assert _VALIDATOR.validate({"p": "two", "params": {}}, schema) == {
        "p": "two",
        "params": {"w": 2},
    }

由于此断言错误,测试失败了:

AssertionError: assert {'p': 'two', 'params': {'q': 1, 'w': 2}} == {'p': 'two', 'params': {'w': 2}}
  +{'p': 'two', 'params': {'q': 1, 'w': 2}}
  -{'p': 'two', 'params': {'w': 2}}
  Full diff:
  - {'p': 'two', 'params': {'w': 2}}
  + {'p': 'two', 'params': {'q': 1, 'w': 2}}
  ?

因此,我们可以看到,尽管第一个亚chema是无效的,但从其“ params”中的默认值(“ q”)是放。 通过一些调试,我发现当您仅覆盖“属性”验证器时,它就会缺乏上下文。因此,当第一个子场“参数”得到验证时,我没有上下文告诉我“ p”参数验证失败,我们仍处于同一子策略中。

请给我任何有关我可以尝试的东西的见解。

I try to make a validator that would set defaults from a JSON schema during validation.

I found this question: Trying to make JSON Schema validator in Python to set default values and adjusted it a bit.
Since I use "jsonschema==3.2.0", I came up with such a code:

def _with_default_setter_extension(validator_class):
    """Extend validator class with defaults setter.

    With this extension, the validator class will set all defaults from a
    schema being validated to a validated instance.
    """

    def _set_defaults(validator, properties, instance, schema):
        if not validator.is_type(instance, "object"):
            return

        valid = True
        for prop, subschema in properties.items():
            if prop in instance:
                for error in validator.descend(
                    instance[prop],
                    subschema,
                    path=prop,
                    schema_path=prop,
                ):
                    valid = False
                    yield error

        # set defaults only when validation is successful
        if valid:
            # set root default when instance is empty
            if not instance and "default" in schema:
                instance.update(schema["default"])
                return

            for prop, subschema in properties.items():
                if "default" in subschema and not isinstance(instance, list):
                    instance.setdefault(prop, subschema["default"])

    return jsonschema.validators.extend(
        validator_class, {"properties": _set_defaults}
    )

It works good except one case which is important for me. I wrote such a test to prove it does not work for my case:

def test_defaults_from_oneOf_only_defaults_from_valid_schema_are_set():
    """When oneOf is used, I expect only defaults from the valid subschema to be set."""
    schema = {
        "oneOf": [
            {
                "properties": {
                    "p": {"enum": ["one"]},
                    "params": {"properties": {"q": {"default": 1}}},
                }
            },
            {
                "properties": {
                    "p": {"enum": ["two"]},
                    "params": {"properties": {"w": {"default": 2}}},
                }
            },
        ],
    }
    assert _VALIDATOR.validate({"p": "two", "params": {}}, schema) == {
        "p": "two",
        "params": {"w": 2},
    }

The test fails with this assertion error:

AssertionError: assert {'p': 'two', 'params': {'q': 1, 'w': 2}} == {'p': 'two', 'params': {'w': 2}}
  +{'p': 'two', 'params': {'q': 1, 'w': 2}}
  -{'p': 'two', 'params': {'w': 2}}
  Full diff:
  - {'p': 'two', 'params': {'w': 2}}
  + {'p': 'two', 'params': {'q': 1, 'w': 2}}
  ?

So we can see, that despite the first subschema is invalid, the default value ("q") from its "params" is set.
With some debugging, I discovered that when you override only the "properties" validator, it lacks context. So when the first subschema "params" gets validated, I have no context telling me that "p" param validation failed and we are still in the same subschema.

Please, give me any insight into what I could try.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

寒冷纷飞旳雪 2025-02-19 01:48:21

You can find the words in the original jsonschema

在此代码中,我们将默认属性添加到每个对象之前
属性已验证,因此默认值本身将需要
在模式下有效。

您正在使用扩展验证器运行验证和set_default。这就是为什么DICT仍会更新的原因。您可能需要使用分离的验证器来运行它们,仅首先进行验证。

You can find the words in the original jsonschema Q&A doc:

In this code, we add the default properties to each object before the
properties are validated, so the default values themselves will need
to be valid under the schema.

And you are running the validation and set_default with the extended validator. That is why the dict is updated still. You might want to run them with a separated validator for validation only first.

北城孤痞 2025-02-19 01:48:21

您是否考虑过在实例中首先填写所有默认值然后验证它,而不是在验证期间设置默认值?

您可以使用fill_default来自 /code>(我创建的一个软件包)以填充具有其架构的现有实例中的所有丢失默认值:

pip install jsonschema-fill-default
from jsonschema_fill_default import fill_default

schema = {
    "type": "object",
    "properties": {
        "key_1": {},
        "key_2": {
            "type": "string",
            "default": "do_not_overwrite_if_key_exists",
        },
        "key_3": {
            "type": "string",
            "default": "use_it_if_key_does_not_exist",
        },
    },
    "required": ["key_1"],
}

json_dict = {"key_1": "key_1_value", "key_2": "key_2_value"}

fill_default(json_dict, schema)  # Mutates instance!
>>> json_dict

{"key_1": "key_1_value", "key_2": "key_2_value", "key_3": "use_it_if_key_does_not_exist"}

您显示的函数仅填充“属性”中的默认值“ properties”的嵌套组合“ allof”“ anyof”,“ Oneof” ,“ DepententsChemas”“ if-then(-else)”“ prefixItems”“ tocke>'items' << a href =“ https://json-schema.org/draft/2020-12” rel =“ nofollow noreferrer”>草稿2020-12 json schema

Instead of setting defaults during validation, have you considered first filling all defaults in an instance and then validating it?

You can use fill_default from jsonschema-fill-default (a package I created) to fill all missing defaults in an existing instance with its schema:

pip install jsonschema-fill-default
from jsonschema_fill_default import fill_default

schema = {
    "type": "object",
    "properties": {
        "key_1": {},
        "key_2": {
            "type": "string",
            "default": "do_not_overwrite_if_key_exists",
        },
        "key_3": {
            "type": "string",
            "default": "use_it_if_key_does_not_exist",
        },
    },
    "required": ["key_1"],
}

json_dict = {"key_1": "key_1_value", "key_2": "key_2_value"}

fill_default(json_dict, schema)  # Mutates instance!
>>> json_dict

{"key_1": "key_1_value", "key_2": "key_2_value", "key_3": "use_it_if_key_does_not_exist"}

The function you show only fills defaults in "properties", whereas fill_default works with all nested combinations of "properties", "allOf", "anyOf", "oneOf", "dependentSchemas", "if-then(-else)", "prefixItems", and "items" keywords of Draft 2020-12 JSON Schema.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文