Keep a datetime.date in 'yyyy-mm-dd' format when using Flask's jsonify
Question:
For some reason, the jsonify
function is converting my datetime.date
to what appears to be an HTTP date. How can I keep the date in yyyy-mm-dd
format when using jsonify
?
test_date = datetime.date(2017, 4, 27)
print(test_date) # 2017-04-27
test_date_jsonify = jsonify(test_date)
print(test_date_jsonify.get_data(as_text=True)) # Thu, 27 Apr 2017 00:00:00 GMT
As suggested in the comments, using jsonify(str(test_date))
returns the desired format. However, consider the following case:
test_dict = {"name": "name1", "date":datetime.date(2017, 4, 27)}
print(test_dict) # {"name": "name1", "date":datetime.date(2017, 4, 27)}
test_dict_jsonify = jsonify(test_dict)
print(test_dict_jsonify.get_data(as_text=True)) # {"date": "Thu, 27 Apr 2017 00:00:00 GMT", "name": "name1"}
test_dict_jsonify = jsonify(str(test_dict))
print(test_dict_jsonify.get_data(as_text=True)) # "{"date": datetime.date(2017, 4, 27), "name": "name1"}"
In this case, the str()
solution does not work.
Answers:
You can change your app’s .json_encoder
attribute, implementing a variant of JSONEncoder
that formats dates as you see fit.
edit: this answer is now too old for Flask versions 2.3+.
for those newer versions, instead customize json_provider_class
; reference: https://flask.palletsprojects.com/en/2.2.x/api/?highlight=json_encoder#flask.Flask.json_provider_class
Following this snippet you can do this:
from flask.json import JSONEncoder
from datetime import date
class CustomJSONEncoder(JSONEncoder):
def default(self, obj):
try:
if isinstance(obj, date):
return obj.isoformat()
iterable = iter(obj)
except TypeError:
pass
else:
return list(iterable)
return JSONEncoder.default(self, obj)
app = Flask(__name__)
app.json_encoder = CustomJSONEncoder
Route:
import datetime as dt
@app.route('/', methods=['GET'])
def index():
now = dt.datetime.now()
return jsonify({'now': now})
datetime.date
is not a JSON type, so it’s not serializable by default. Instead, Flask adds a hook to dump the date to a string in RFC 1123 format, which is consistent with dates in other parts of HTTP requests and responses.
Use a custom JSON encoder if you want to change the format. Subclass JSONEncoder
and set Flask.json_encoder
to it.
from flask import Flask
from flask.json import JSONEncoder
class MyJSONEncoder(JSONEncoder):
def default(self, o):
if isinstance(o, date):
return o.isoformat()
return super().default(o)
class MyFlask(Flask):
json_encoder = MyJSONEncoder
app = MyFlask(__name__)
It is a good idea to use ISO 8601 to transmit and store the value. It can be parsed unambiguously by JavaScript Date.parse
(and other parsers). Choose the output format when you output, not when you store.
A string representing an RFC 2822 or ISO 8601 date (other formats may be used, but results may be unexpected).
When you load the data, there’s no way to know the value was meant to be a date instead of a string (since date is not a JSON type), so you don’t get a datetime.date
back, you get a string. (And if you did get a date, how would it know to return date
instead of datetime
?)
Flask 2.2 shows a deprecation warning
'JSONEncoder' is deprecated and will be removed in Flask 2.3. Use 'Flask.json' to provide an alternate JSON implementation instead.
An update is needed to remove it and/or have it work in Flask 2.3+. Another example from the Flask repository here
from datetime import datetime, date
from flask import Flask
from flask.json.provider import DefaultJSONProvider
class UpdatedJSONProvider(DefaultJSONProvider):
def default(self, o):
if isinstance(o, date) or isinstance(o, datetime):
return o.isoformat()
return super().default(o)
app = Flask(__name__)
app.json = UpdatedJSONProvider(app)
For some reason, the jsonify
function is converting my datetime.date
to what appears to be an HTTP date. How can I keep the date in yyyy-mm-dd
format when using jsonify
?
test_date = datetime.date(2017, 4, 27)
print(test_date) # 2017-04-27
test_date_jsonify = jsonify(test_date)
print(test_date_jsonify.get_data(as_text=True)) # Thu, 27 Apr 2017 00:00:00 GMT
As suggested in the comments, using jsonify(str(test_date))
returns the desired format. However, consider the following case:
test_dict = {"name": "name1", "date":datetime.date(2017, 4, 27)}
print(test_dict) # {"name": "name1", "date":datetime.date(2017, 4, 27)}
test_dict_jsonify = jsonify(test_dict)
print(test_dict_jsonify.get_data(as_text=True)) # {"date": "Thu, 27 Apr 2017 00:00:00 GMT", "name": "name1"}
test_dict_jsonify = jsonify(str(test_dict))
print(test_dict_jsonify.get_data(as_text=True)) # "{"date": datetime.date(2017, 4, 27), "name": "name1"}"
In this case, the str()
solution does not work.
You can change your app’s .json_encoder
attribute, implementing a variant of JSONEncoder
that formats dates as you see fit.
edit: this answer is now too old for Flask versions 2.3+.
for those newer versions, instead customize json_provider_class
; reference: https://flask.palletsprojects.com/en/2.2.x/api/?highlight=json_encoder#flask.Flask.json_provider_class
Following this snippet you can do this:
from flask.json import JSONEncoder
from datetime import date
class CustomJSONEncoder(JSONEncoder):
def default(self, obj):
try:
if isinstance(obj, date):
return obj.isoformat()
iterable = iter(obj)
except TypeError:
pass
else:
return list(iterable)
return JSONEncoder.default(self, obj)
app = Flask(__name__)
app.json_encoder = CustomJSONEncoder
Route:
import datetime as dt
@app.route('/', methods=['GET'])
def index():
now = dt.datetime.now()
return jsonify({'now': now})
datetime.date
is not a JSON type, so it’s not serializable by default. Instead, Flask adds a hook to dump the date to a string in RFC 1123 format, which is consistent with dates in other parts of HTTP requests and responses.
Use a custom JSON encoder if you want to change the format. Subclass JSONEncoder
and set Flask.json_encoder
to it.
from flask import Flask
from flask.json import JSONEncoder
class MyJSONEncoder(JSONEncoder):
def default(self, o):
if isinstance(o, date):
return o.isoformat()
return super().default(o)
class MyFlask(Flask):
json_encoder = MyJSONEncoder
app = MyFlask(__name__)
It is a good idea to use ISO 8601 to transmit and store the value. It can be parsed unambiguously by JavaScript Date.parse
(and other parsers). Choose the output format when you output, not when you store.
A string representing an RFC 2822 or ISO 8601 date (other formats may be used, but results may be unexpected).
When you load the data, there’s no way to know the value was meant to be a date instead of a string (since date is not a JSON type), so you don’t get a datetime.date
back, you get a string. (And if you did get a date, how would it know to return date
instead of datetime
?)
Flask 2.2 shows a deprecation warning
'JSONEncoder' is deprecated and will be removed in Flask 2.3. Use 'Flask.json' to provide an alternate JSON implementation instead.
An update is needed to remove it and/or have it work in Flask 2.3+. Another example from the Flask repository here
from datetime import datetime, date
from flask import Flask
from flask.json.provider import DefaultJSONProvider
class UpdatedJSONProvider(DefaultJSONProvider):
def default(self, o):
if isinstance(o, date) or isinstance(o, datetime):
return o.isoformat()
return super().default(o)
app = Flask(__name__)
app.json = UpdatedJSONProvider(app)