Skip to content

Commit 3174367

Browse files
committed
Handle nullable fields properly
The thing description is invalid if we have fields with `allow_none` enabled. That's because JSONSchema permits `type` to be a list, which is the sensible way to represent a field that may be `null` or a value. I've modified our marshmallow_jsonschema library to expand this into one duplicated sub-schema for each type in the list, combined using `oneOf`. This is allowed by the thing description (and, as far as I know, is valid JSON Schema as well).
1 parent 52695d5 commit 3174367

File tree

2 files changed

+32
-4
lines changed

2 files changed

+32
-4
lines changed

src/labthings/json/marshmallow_jsonschema/base.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import datetime
22
import decimal
33
import uuid
4+
from copy import deepcopy
45
from inspect import isclass
56

67
from marshmallow import Schema, fields, missing, validate
@@ -68,6 +69,26 @@
6869
}
6970

7071

72+
def convert_type_list_to_oneof(schema):
73+
"""Avoid setting `type` to a list, using `oneOf`
74+
75+
JSONSchema allows datatypes to be specified as lists, which
76+
means the type may be any of the specified values. This is
77+
not permitted by W3C Thing Description. This function will
78+
use the oneOf mechanism in JSONSchema to convert schemas
79+
that use a list of types into a series of schemas each with
80+
one type. Those schemas are then nested together using
81+
OneOf.
82+
"""
83+
if "type" not in schema or type(schema["type"]) != list:
84+
return schema
85+
subschemas = []
86+
for t in schema["type"]:
87+
subschemas.append(deepcopy(schema))
88+
subschemas[-1]["type"] = t
89+
return {"oneOf": subschemas}
90+
91+
7192
class JSONSchema(Schema):
7293
"""Converts to JSONSchema as defined by http://json-schema.org/."""
7394

@@ -202,6 +223,8 @@ def _get_schema_for_field(self, obj, field):
202223
)
203224
if base_class is not None and base_class in FIELD_VALIDATORS:
204225
schema = FIELD_VALIDATORS[base_class](schema, field, validator, obj)
226+
if "type" in schema and type(schema["type"]) == list:
227+
schema = convert_type_list_to_oneof(schema)
205228
return schema
206229

207230
def _from_nested_schema(self, _, field):

tests/test_json_marshmallow_jsonschema.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -289,18 +289,23 @@ class TestSchema(Schema):
289289

290290

291291
def test_allow_none():
292-
"""A field with allow_none set to True should have type null as additional."""
292+
"""A field with allow_none set to True should be able to be null.
293+
294+
Note that this has been modified from JSONSchema behaviour: in
295+
JSONSchema, "type" should be set to `["string", "null"]`. This
296+
is not allowed in Thing Descriptions, so instead we need to use
297+
`oneOf`. Our modified JSON schema library does this."""
293298

294299
class TestSchema(Schema):
295300
id = fields.Integer(required=True)
296-
readonly_fld = fields.String(allow_none=True)
301+
nullable_fld = fields.String(allow_none=True)
297302

298303
schema = TestSchema()
299304

300305
dumped = validate_and_dump(schema)
301306

302-
assert dumped["properties"]["readonly_fld"] == {
303-
"type": ["string", "null"],
307+
assert dumped["properties"]["nullable_fld"] == {
308+
"oneOf": [{"type": "string"}, {"type": "null"}],
304309
}
305310

306311

0 commit comments

Comments
 (0)