Python flask json schema validation
Question:
I am using the flask framework in my new project. It would get JSON data from the post and send a JSON response. So, I need to validate my JSON request.
I have seen couple of libraries. But those libraries are not working as expected. Finally, I have decided to go with a flask-jsonschema-validator
. It is working fine with a single JSON object. If the request object has a nested object, it is not working.
For example:
from flask_jsonschema_validator import JSONSchemaValidator
JSONSchemaValidator(app=app, root="schemas")
This is my initialization of the validator:
# If any error occurred in the request.
@app.errorhandler(jsonschema.ValidationError)
def json_validation_error(e):
return json_response("error", str(e), {})
This is my error handler
@app.validate('model', 'save')
def save_model():
This is my implementation:
{
"save": {
"type": "object",
"properties": {
"workspace": {"type": "object"},
"name": {"type": "string"},
"description": {"type": "string"},
"uri": {"type": "string"},
"type": {
"name": {"type": "string"},
}
},
"required": [ "workspace", "name", "description", "uri", "type"]
}
}
This is my model.json
file. It is validating the request except for the "type". How to apply validation for JSON request with nested object.
Answers:
flask-expects-json package checks variables types on nested objects.
It work as a decorator on your route.
SCHEMA = {
"type": "object",
"properties": {
"workspace": {"type": "object"},
"name": {"type": "string"},
"description": {"type": "string"},
"uri": {"type": "string"},
"type": {
"type": "object",
"properties": {
"name": {"type": "string"},
}
}
},
"required": ["workspace", "name", "description", "uri", "type"]
}
@expects_json(SCHEMA)
def my_route(self, **kwargs):
pass
If the validation of complex nested objects, I would recommend an alternative tool to JSONSchema. I can replicate your schema validation in GoodJSON like below. As you can see, formulating a validation schema is just composing a bunch of self-contained validator functions that can be applied to objects, lists and primitive values.
from goodjson.validators import is_dict, is_string, is_uri, foreach_key
validate_fun = foreach_key(
save=[foreach_key(
workspace=[is_dict],
name=[is_string],
description=[is_string],
uri=[is_uri],
type=[foreach_key(
name=[is_string]
)]
)]
)
validate_fun(YOUR_JSON_DATA_OBJECT)
Disclaimer: I am the author of GoodJSON.
Here’s how I do it using dataclasses
, it requires a library called dataclass_utils, which can be installed using pip install dataclass_utils
from dataclasses import dataclass
from functools import wraps
from typing import Any
from dataclass_utils.type_checker import check_dataclass
from flask import Flask
app = Flask(__name__)
@dataclass
class Schema:
username: str
some_field: str
some_number: int
def validate_schema(the_dataclass: Any):
def _validate_schema(org_func):
@wraps(org_func)
def wrapper(*args, **kwargs):
from flask import request
td = the_dataclass(**request.json)
err = check_dataclass(td, type(td))
if err is not None:
return "schema invalid", 400
return org_func(*args, **kwargs)
return wrapper
return _validate_schema
@app.route("/someApiEndpoint", methods=["POST"])
@validate_schema(the_dataclass=Schema)
def someApiEndpoint():
return "yay the schema is valid", 200
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080, debug=True)
I am using the flask framework in my new project. It would get JSON data from the post and send a JSON response. So, I need to validate my JSON request.
I have seen couple of libraries. But those libraries are not working as expected. Finally, I have decided to go with a flask-jsonschema-validator
. It is working fine with a single JSON object. If the request object has a nested object, it is not working.
For example:
from flask_jsonschema_validator import JSONSchemaValidator
JSONSchemaValidator(app=app, root="schemas")
This is my initialization of the validator:
# If any error occurred in the request.
@app.errorhandler(jsonschema.ValidationError)
def json_validation_error(e):
return json_response("error", str(e), {})
This is my error handler
@app.validate('model', 'save')
def save_model():
This is my implementation:
{
"save": {
"type": "object",
"properties": {
"workspace": {"type": "object"},
"name": {"type": "string"},
"description": {"type": "string"},
"uri": {"type": "string"},
"type": {
"name": {"type": "string"},
}
},
"required": [ "workspace", "name", "description", "uri", "type"]
}
}
This is my model.json
file. It is validating the request except for the "type". How to apply validation for JSON request with nested object.
flask-expects-json package checks variables types on nested objects.
It work as a decorator on your route.
SCHEMA = {
"type": "object",
"properties": {
"workspace": {"type": "object"},
"name": {"type": "string"},
"description": {"type": "string"},
"uri": {"type": "string"},
"type": {
"type": "object",
"properties": {
"name": {"type": "string"},
}
}
},
"required": ["workspace", "name", "description", "uri", "type"]
}
@expects_json(SCHEMA)
def my_route(self, **kwargs):
pass
If the validation of complex nested objects, I would recommend an alternative tool to JSONSchema. I can replicate your schema validation in GoodJSON like below. As you can see, formulating a validation schema is just composing a bunch of self-contained validator functions that can be applied to objects, lists and primitive values.
from goodjson.validators import is_dict, is_string, is_uri, foreach_key
validate_fun = foreach_key(
save=[foreach_key(
workspace=[is_dict],
name=[is_string],
description=[is_string],
uri=[is_uri],
type=[foreach_key(
name=[is_string]
)]
)]
)
validate_fun(YOUR_JSON_DATA_OBJECT)
Disclaimer: I am the author of GoodJSON.
Here’s how I do it using dataclasses
, it requires a library called dataclass_utils, which can be installed using pip install dataclass_utils
from dataclasses import dataclass
from functools import wraps
from typing import Any
from dataclass_utils.type_checker import check_dataclass
from flask import Flask
app = Flask(__name__)
@dataclass
class Schema:
username: str
some_field: str
some_number: int
def validate_schema(the_dataclass: Any):
def _validate_schema(org_func):
@wraps(org_func)
def wrapper(*args, **kwargs):
from flask import request
td = the_dataclass(**request.json)
err = check_dataclass(td, type(td))
if err is not None:
return "schema invalid", 400
return org_func(*args, **kwargs)
return wrapper
return _validate_schema
@app.route("/someApiEndpoint", methods=["POST"])
@validate_schema(the_dataclass=Schema)
def someApiEndpoint():
return "yay the schema is valid", 200
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080, debug=True)