Dodomac Dodomac - 3 years ago 105
JSON Question

How to specify which oneOf item a JSON object should take?

Using Python and

jsonschema
I am trying to validate the assignment of
ObjA
or
ObjB
etc. to
beta
(
test.json
)

{
"alpha": {

"beta": "ObjA"
}
}


In my schema (
testschema.json
)
beta
is
oneOf
a number of items and each item is defined as below (with differing values for
a
,
b
, and
c
)

"ObjA": {

"type": "object",
"properties": {

"items": {

"a": [90, 95],
"b": [4, 8],
"c": [0.2, 0.6]
}
},

"additionalProperties": false
}


That is to say,
beta
can take on
oneOf
values that are
ObjA
,
ObjB
,
ObjC
and
ObjD
. I am simply trying to specify which one it should use in
test.json


"alpha": {

"type": "object",
"properties": {

"beta": {

"oneOf": [
{
"type": "object",
"properties": {

"ObjA": {

"type": "object",
"properties": {

"items": {

"a": [90, 95],
"b": [4, 8],
"c": [0.2, 0.6]
}
},

"additionalProperties": false
}
},

"additionalProperties": false
},

{
"type": "object",
"properties": {

"ObjB": {

"type": "object",
"properties": {

"items": {

"a": [100],
"b": [0],
"c": [0]
}
},

"additionalProperties": false
}
}
},

...
ObjC and ObjD defined
...
}
}
}
},


However, when trying to validate against the schema using
jsonschema.validate()


### Test the whole JSON is valid against the Schema
def test_valid__JSON_against_schema(self):

with open(schema_filename) as schema_file:
test_schema = json.load('testschema.json')
schema_file.close()

with open(json_filename) as json_file:
test_json = json.load('test.json')
json_file.close()

validate(test_json, test_schema)


I get the following error

Failed validating 'oneOf' in schema['properties']['alpha']['properties']['beta']:


Here is the whole message

E
======================================================================
ERROR: test_valid__JSON_against_schema (__main__.SchemaTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_test-variables.py", line 35, in test_valid__JSON_against_schema
validate(test_json, test_schema)
File "/local/tools/PACKAGES/python3/lib/python3.6/site-packages/jsonschema/validators.py", line 541, in validate
cls(schema, *args, **kwargs).validate(instance)
File "/local/tools/PACKAGES/python3/lib/python3.6/site-packages/jsonschema/validators.py", line 130, in validate
raise error
jsonschema.exceptions.ValidationError: 'ObJA' is not valid under any of the given schemas

Failed validating 'oneOf' in schema['properties']['alpha']['properties']['beta']:
{'oneOf': [{'additionalProperties': False,
'properties': {'ObjA': {'additionalProperties': False,
'properties': {'items': {'a': [0.2, 0.6],
'b': [90, 95],
'c': [4, 8]}},
'type': 'object'}},
'type': 'object'},
{'additionalProperties': False,
'properties': {'ObjB': {'additionalProperties': False,
'properties': {'items': {'a': [0],
'b': [100],
'c': [0]}},
'type': 'object'}},
'type': 'object'},
{'additionalProperties': False,
'properties': {'ObjC': {'additionalProperties': False,
'properties': {'items': {'a': [0],
'b': [50],
'c': [50]}},
'type': 'object'}},
'type': 'object'},
{'additionalProperties': False,
'properties': {'ObjD': {'additionalProperties': False,
'properties': {'items': {'a': [100],
'b': [0],
'c': [0]}},
'type': 'object'}},
'type': 'object'}]}

On instance['alpha']['beta']:
'ObjA'

----------------------------------------------------------------------
Ran 1 test in 0.007s

FAILED (errors=1)


Using the online
jsonschema
validator (http://json-schema-validator.herokuapp.com/)
test.json
does not validate, so I removed any mention of
alpha
from the file (i.e. to this
{ }
) and the validator reported the following

[ {
"level" : "warning",
"schema" : {
"loadingURI" : "#",
"pointer" : "/properties/alpha/properties/beta/oneOf/0/properties/ObjA/properties/items"
},
"domain" : "syntax",
"message" : "the following keywords are unknown and will be ignored: [a, b, c]",
"ignored" : [ "a", "b", "c" ]
} ]


Restoring
test.json
back, the validation gives

[ {
"level" : "warning",
"schema" : {
"loadingURI" : "#",
"pointer" : "/properties/alpha/properties/beta/oneOf/0/properties/ObjA/properties/items"
},
"domain" : "syntax",
"message" : "the following keywords are unknown and will be ignored: [a, b, c]",
"ignored" : [ "a", "b", "c" ]
}, {
"level" : "warning",
"schema" : {
"loadingURI" : "#",
"pointer" : "/properties/alpha/properties/beta/oneOf/0/properties/ObjA/properties/items"
},
"domain" : "syntax",
"message" : "the following keywords are unknown and will be ignored: [a, b, c]",
"ignored" : [ "a", "b", "c" ]
}, {
"level" : "warning",
"schema" : {
"loadingURI" : "#",
"pointer" : "/properties/alpha/properties/beta/oneOf/0/properties/ObjA/properties/items"
},
"domain" : "syntax",
"message" : "the following keywords are unknown and will be ignored: [a, b, c]",
"ignored" : [ "a", "b", "c" ]
}, {
"level" : "error",
"schema" : {
"loadingURI" : "#",
"pointer" : "/properties/alpha/properties/beta"
},
"instance" : {
"pointer" : "/alpha/beta"
},
"domain" : "validation",
"keyword" : "oneOf",
"message" : "instance failed to match exactly one schema (matched 0 out of 1)",
"matched" : 0,
"nrSchemas" : 1,
"reports" : {
"/properties/alpha/properties/beta/oneOf/0" : [ {
"level" : "warning",
"schema" : {
"loadingURI" : "#",
"pointer" : "/properties/alpha/properties/beta/oneOf/0/properties/ObjA/properties/items"
},
"domain" : "syntax",
"message" : "the following keywords are unknown and will be ignored: [a, b, c]",
"ignored" : [ "a", "b", "c" ]
}, {
"level" : "error",
"schema" : {
"loadingURI" : "#",
"pointer" : "/properties/alpha/properties/beta/oneOf/0"
},
"instance" : {
"pointer" : "/alpha/beta"
},
"domain" : "validation",
"keyword" : "type",
"message" : "instance type (string) does not match any allowed primitive type (allowed: [\"object\"])",
"found" : "string",
"expected" : [ "object" ]
} ]
}
} ]


Does anyone know the correct way of doing this?

Thanks.

Answer Source

Let's isolate the part of the schema that is failing.

{
  "type": "object",
  "properties": {
    "ObjA": {
      "type": "object",
      "properties": {
        "items": {
          "a": [90, 95],
          "b": [4, 8],
          "c": [0.2, 0.6]
        }
      },
      "additionalProperties": false
    }
  },
  "additionalProperties": false
}

Which is valiating this part of you test data

"ObjA"

The error you are seeing is telling you that the test data is a string, but the schema requires that it be an object.

Test data that matches your schema would look something like this

{
  "ObjA": {
    "items": ???
  }
}

I use ??? here because the value here can be any value that is valid JSON. The reason is because this schema does not contain any JSON Schema keywords.

{
  "a": [90, 95],
  "b": [4, 8],
  "c": [0.2, 0.6]
}

Therefore, there are no constraints on what value it can be. The warning messages you are seeing are telling you that a, b and c are not keywords.

I don't know what you are trying to express with this schema, but it's a far cry from the simple string in your test data.

Edit in response to comments

It sounds like you are trying to use JSON Schema for something it isn't designed for. The schema should describe only what the user needs to be concerned about. You will have to map the user values to hard coded structure in another step. In any case, it sounds like what you need is enum rather than oneOf.

{
  "enum": ["ObjA", "ObjB", "ObjC", "ObjD"]
}

or

{
  "enum": [
    {
      "a": [90, 95],
      "b": [4, 8],
      "c": [0.2, 0.6]
    },
    {
      "a": [100],
      "b": [0],
      "c": [0]
    },
    ...
  ]
}

There is no way to have both. If you really need to express that in your schema, I would add a custom keyword that shows the mapping. A validator will ignore it (and you might get a warning), but the relationship will be expressed for human readers and perhaps custom tools. It might look something like this

{
  "enum": ["ObjA", "ObjB", "ObjC", "ObjD"]
  "enumValues": {
    "ObjA": {
      "a": [90, 95],
      "b": [4, 8],
      "c": [0.2, 0.6]
    },
    "ObjB": {
      "a": [100],
      "b": [0],
      "c": [0]
    },
    ...
  ]
}

The enum tells the user what values are allowed and the validator can check this. Then the enumValues keyword is something we made up to express the relationship between the enum values and their actual values.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download