I have setup a Flask project using the flask restx to help me validate the input JSON payload. Note: I am also using restx's Namespace for other things too, so I have ns
instead of blueprint
I have something like this:
@ns.route('/event')
class EventResource(Resource):
@ns.expect(event_model, validate=True)
def post(self):
validated_data = ns.payload # Use the payload directly from the request
logger.info(f"Validated event data: {validated_data}")
...
For the model, I have a nested model like this:
event_department_model = ns.model('EventDepartment', {
'identifier': fields.String(required=True, description='Department identifier'),
'name': fields.String(required=True, description='Department name')
})
event_model = ns.model('Event', {
...
'department': fields.Nested(event_department_model, required=False, allow_null=True, skip_none=True, nullable=True, description='Department information'),
...
}
I want department
in the input JSON payload to accept null
. So in event_model
, I have already tried required=False
, allow_null=True
, nullable=True
, skip_none=True
and different combinations of these options.
However, no matter what combinations I have tried, I am getting the same error:
{
"errors": {
"department": "None is not of type 'object'"
},
"message": "Input payload validation failed"
}
Did some searching online but I couldn't find anything similar to my issue. My online search result tells me that it is not liking the null
value in the JSON because it is expecting an object... even though I have already put down options such as required=False
, allow_null=True
. I see people usually get this resolved by setting something like allow_null
or skip_none
. I am not sure what went wrong.
Tracing the responses from your GitHub Issue to flask-restx, it looks like it is a fundamental limitation of how schemas are handled in flask-restx
. However, a workaround was proposed to flask-restplus (which flask-restx
is a fork of), and that same approach can be modified to fit the current flask-restx
. The approach is to create a subclass of Nested
(this could be simplified for the use-case, this was written based on matching the base fields.Nested
's schema method):
class NullableNested(fields.Nested):
def schema(self):
schema = super().schema()
if self.as_list:
ref = schema['items'].pop('$ref')
schema['items']['anyOf'] = [
{'$ref': ref},
{'type': 'null'}
]
return schema
elif any(schema.values()):
all_of = schema.pop('allOf')
return {
'anyOf': [
{'allOf': all_of},
{'type': 'null'}
]
}
else:
ref = schema.pop('$ref')
return {
'anyOf': [
{'$ref': ref},
{'type': 'null'}
]
}
In total, for the minimal reproducible example:
from flask import Flask
from flask_restx import Api, Resource, fields
app = Flask(__name__)
api = Api(app, doc='/docs')
ns = api.namespace('test', description='Test namespace')
class NullableNested(fields.Nested):
def schema(self):
schema = super().schema()
if self.as_list:
ref = schema['items'].pop('$ref')
schema['items']['anyOf'] = [
{'$ref': ref},
{'type': 'null'}
]
return schema
elif any(schema.values()):
all_of = schema.pop('allOf')
return {
'anyOf': [
{'allOf': all_of},
{'type': 'null'}
]
}
else:
ref = schema.pop('$ref')
return {
'anyOf': [
{'$ref': ref},
{'type': 'null'}
]
}
# Define the nested model
event_department_model = ns.model('EventDepartment', {
'identifier': fields.String(required=True, description='Department identifier'),
'name': fields.String(required=True, description='Department name')
})
# Define the main event model
event_model = ns.model('Event', {
'event_id': fields.String(required=True, description='Event ID'),
'department': NullableNested(
event_department_model,
required=False,
description='Department info (can be null)',
allow_null=True, # Tried this
skip_none=True, # And this
nullable=True # And this too
)
})
@ns.route('/event')
class EventResource(Resource):
@ns.expect(event_model, validate=True)
def post(self):
validated_data = ns.payload
return {'received': validated_data}, 200
api.add_namespace(ns)
if __name__ == '__main__':
app.run(debug=True)
Then if you run:
curl -X POST 'http://127.0.0.1:5000/test/event' --data '{
"event_id": "abc123",
"department": null
}' -H 'Content-Type: application/json'
The output is the expected:
{
"received": {
"event_id": "abc123",
"department": null
}
}