Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trying to make JSON Schema validator in Python to set default values

I'm using a slightly modified version of the code from the JSON Schema FAQ to create a validator that sets default values:

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},
    )
DefaultValidatingDraft4Validator = extend_with_default(Draft4Validator)

And I have a JSON Schema like so:

{'definitions': {
  'obj': {'additionalProperties': False,
          'properties': {
            'foo': {'default': None, 'oneOf': [{'type': 'null'}, {'type': 'string'}]},
            'bar': {'default': None, 'oneOf': [{'type': 'null'}, {'type': 'string'}]},
            'baz': {'default': None, 'oneOf': [{'type': 'null'}, {'type': 'string'}]},
            'children': {'default': None, 'oneOf': [
              {'type': 'null'}, 
              {
                'items': {'$ref': '#/definitions/obj'},
                'minItems': 1, 
                'type': 'array'
              }
            ]}},
  'required': ['foo', 'bar', 'baz'],
  'type': 'object'}},
  'oneOf': [
    {'$ref': '#/definitions/obj'},
    {
      'items': {'$ref': '#/definitions/obj'},
      'minItems': 1, 
      'type': 'array'
    }
  ]
}

So basically, there's an object that can have foo/bar/baz fields, and the entire instance can either be one of those objects or a list of them. Additionally, each object can have a list of child objects in the children field.

When I try to run this code against a single object, it works fine, but it fails when I have a list of objects:

In [22]: DefaultValidatingDraft4Validator(schema).validate({'foo': 'hi'})

In [23]: DefaultValidatingDraft4Validator(schema).validate([{'foo': 'hi'}, {'baz': 'bye'}])

...
AttributeError: 'list' object has no attribute 'setdefault'

With the "children" field, I need a way to handle lists at every level of the schema validation. Is there a way to do that properly?

like image 547
shroud Avatar asked Oct 29 '22 15:10

shroud


1 Answers

In the validator, the list that is causing the exception, is a valid element.

Changes Needed:

So you need to exclude the list from consideration by changing:

if "default" in subschema:
    instance.setdefault(property_, subschema["default"])

to:

if "default" in subschema and not isinstance(instance, list):
    instance.setdefault(property_, subschema["default"])

This was all that was needed to get the two test cases to pass.

Code:

from jsonschema import Draft4Validator, 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 and not isinstance(instance, list):
                instance.setdefault(property_, subschema["default"])

        for error in validate_properties(
            validator, properties, instance, schema,
        ):
            yield error

    return validators.extend(
        validator_class, {"properties": set_defaults},
    )
FillDefaultValidatingDraft4Validator = extend_with_default(Draft4Validator)

Test Code:

test_schema = {
    'definitions': {
        'obj': {'additionalProperties': False,
                'properties': {
                    'foo': {'default': None, 'oneOf': [{'type': 'null'}, {'type': 'string'}]},
                    'bar': {'default': None, 'oneOf': [{'type': 'null'}, {'type': 'string'}]},
                    'baz': {'default': None, 'oneOf': [{'type': 'null'}, {'type': 'string'}]},
                    'children': {'default': None, 'oneOf': [
                        {'type': 'null'},
                        {
                            'items': {'$ref': '#/definitions/obj'},
                            'minItems': 1,
                            'type': 'array'
                        }
                    ]}
                },
                'required': ['foo', 'bar', 'baz'],
                'type': 'object'}
    },
    'oneOf': [
        {'$ref': '#/definitions/obj'},
        {
          'items': {'$ref': '#/definitions/obj'},
          'minItems': 1,
          'type': 'array'
        }
    ]
}

for test_data in ({'foo': 'hi'}, [{'foo': 'hi'}, {'baz': 'bye'}], 
                  [{'children': [{'foo': 'hi'}, {'baz': 'bye'}]}]):
    FillDefaultValidatingDraft4Validator(test_schema).validate(test_data)
    print(test_data)

Results:

{'foo': 'hi', 'bar': None, 'baz': None, 'children': None}
[
    {'foo': 'hi', 'bar': None, 'baz': None, 'children': None}, 
    {'baz': 'bye', 'foo': None, 'bar': None, 'children': None}
]
[
    {'children': [
        {'foo': 'hi', 'bar': None, 'baz': None, 'children': None}, 
        {'baz': 'bye', 'foo': None, 'bar': None, 'children': None}
    ], 'foo': None, 'bar': None, 'baz': None}
]
like image 153
Stephen Rauch Avatar answered Nov 15 '22 06:11

Stephen Rauch