cwacek/python-jsonschema-objects

oneOf support when building objects

Closed this issue · 2 comments

oneOf statements are supported when deserialising from JSON, but not when constructing an object. The library appears to assume the first option when following a oneOf branch, and does not check other branches when a validation failure occurs.

Schema

{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "title": "Schema",
    "type": "object",
    "additionalProperties": false,
    "properties": {
        "type": {
            "type": "string"
        },
        "data": {
            "type": "object"
        }
    },
    "oneOf": [{
            "properties": {
                "type": {
                    "enum": ["A"]
                },
                "data": {
                    "properties": {
                        "bar": {
                            "type": "string"
                        }
                    }
                }
            }
        },
        {
            "properties": {
                "type": {
                    "enum": ["B"]
                },
                "data": {
                    "properties": {
                        "bar": {
                            "type": "number"
                        }
                    }
                }
            }
        }
    ]
}

Python

import python_jsonschema_objects as jso
builder = jso.ObjectBuilder('schema.json')
namespace = builder.build_classes()
Schema = namespace.Schema

instance = Schema.from_json("""
{
    "type": "B",
    "data": {
        "bar": 1
    }
}
""")

instance.validate() # success

instance2 = Schema()
instance2.type = "B" # failure
instance2.data = {
    "bar": 1
}

Stacktrace

Traceback (most recent call last):
  File "schema.py", line 19, in <module>
    instance2.type = "B"
  File "/home/user/.local/lib/python3.6/site-packages/python_jsonschema_objects/classbuilder.py", line 225, in __setattr__
    prop.__set__(self, val)
  File "/home/user/.local/lib/python3.6/site-packages/python_jsonschema_objects/descriptors.py", line 99, in __set__
    validator = info["type"](val)
  File "/home/user/.local/lib/python3.6/site-packages/python_jsonschema_objects/literals.py", line 44, in __init__
    self.validate()
  File "/home/user/.local/lib/python3.6/site-packages/python_jsonschema_objects/literals.py", line 84, in validate
    validator(paramval, self._value, info)
  File "/home/user/.local/lib/python3.6/site-packages/python_jsonschema_objects/validators.py", line 51, in enum
    raise ValidationError("{0} is not one of {1}".format(value, param))
python_jsonschema_objects.validators.ValidationError: B is not one of ['A']

Curiously if you provide the property which causes the branch during initialisation, it succeeds.

instance2 = Schema(type="B") # success
instance2.data = {
	"bar": 1
}

This is unavoidable because when we materialize an object or property that could be one of multiple types, we need to choose a type. The builder can deal with the uncertainty, but the actual object can't.

In the first case, you're not providing a discriminating piece of information, so it's just going to choose the first one that is valid (which boils down to the first one in the list). In the second one, A isn't valid, so it makes it of type B.