Skip to content

Commit

Permalink
Fix pallets#220: adds a default JSONEncoder implementation to Flask.j…
Browse files Browse the repository at this point in the history
…son_encoder_class, which users can override with their own implementation; makes jsonify use current_app.json_encoder_class as its encoder class.
  • Loading branch information
jfinkels committed Apr 1, 2012
1 parent f430039 commit a7d3059
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 4 deletions.
2 changes: 2 additions & 0 deletions CHANGES
Expand Up @@ -8,6 +8,8 @@ Version 0.9

Relase date to be decided, codename to be chosen.

- Added :attr:`flask.Flask.json_encoder_class` so users can customize JSON
encoding during execution of the :func:`flask.jsonify` function.
- The :func:`flask.Request.on_json_loading_failed` now returns a JSON formatted
response by default.
- The :func:`flask.url_for` function now can generate anchors to the
Expand Down
5 changes: 5 additions & 0 deletions docs/api.rst
Expand Up @@ -306,6 +306,9 @@ Message Flashing
Returning JSON
--------------

To customize JSON encoding during execution of the :func:`~flask.jsonify`
function, see :attr:`Flask.json_encoder_class`.

.. autofunction:: jsonify

.. data:: json
Expand Down Expand Up @@ -337,6 +340,8 @@ Returning JSON

Note that the ``|tojson`` filter escapes forward slashes properly.

.. autoclass:: JSONEncoder

Template Rendering
------------------

Expand Down
2 changes: 1 addition & 1 deletion flask/__init__.py
Expand Up @@ -22,7 +22,7 @@
from .config import Config
from .helpers import url_for, jsonify, json_available, flash, \
send_file, send_from_directory, get_flashed_messages, \
get_template_attribute, make_response, safe_join
get_template_attribute, make_response, safe_join, JSONEncoder
from .globals import current_app, g, request, session, _request_ctx_stack
from .ctx import has_request_context
from .module import Module
Expand Down
34 changes: 33 additions & 1 deletion flask/app.py
Expand Up @@ -25,7 +25,7 @@

from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \
locked_cached_property, _tojson_filter, _endpoint_from_view_func, \
find_package
find_package, JSONEncoder
from .wrappers import Request, Response
from .config import ConfigAttribute, Config
from .ctx import RequestContext
Expand Down Expand Up @@ -271,6 +271,38 @@ class Flask(_PackageBoundObject):
#: .. versionadded:: 0.8
session_interface = SecureCookieSessionInterface()

#: The JSONEncoder to use when the :func:`~flask.jsonify` function is
#: called.
#:
#: Flask provides a default implementation which provides serialization for
#: :class:`datetime.datetime` objects to strings in ISO 8601 format.
#:
#: To customize encoding of Python objects to JSON strings, create a
#: subclass of :class:`flask.JSONEncoder` which implements the
#: :meth:`json.JSONEncoder.default` method, then set the
#: :attr:`json_encoder_class` attribute on your Flask application object.
#: For example, if you want a different string encoding of
#: :class:`~datetime.datetime` objects::
#:
#: import datetime
#: import flask
#:
#: class MyEncoder(flask.JSONEncoder):
#: def default(self, obj):
#: if isinstance(obj, datetime.datetime):
#: return obj.strftime('%A, %B %d, %H:%M')
#: return super(MyEncoder, self).default(obj)
#:
#: app = flask.Flask(__name__)
#: app.json_encoder_class = MyEncoder
#:
#: @app.route('/currenttime')
#: def current_time():
#: return flask.jsonify(dict(time=datetime.datetime.now()))
#:
#: .. versionadded:: 0.9
json_encoder_class = JSONEncoder

def __init__(self, import_name, static_path=None, static_url_path=None,
static_folder='static', template_folder='templates',
instance_path=None, instance_relative_config=False):
Expand Down
29 changes: 27 additions & 2 deletions flask/helpers.py
Expand Up @@ -11,6 +11,7 @@

from __future__ import with_statement

import datetime
import imp
import os
import sys
Expand Down Expand Up @@ -142,8 +143,10 @@ def get_current_user():
json_str = json.dumps(dict(*args, **kwargs), indent=None)
content = str(callback) + "(" + json_str + ")"
return current_app.response_class(content, mimetype='application/javascript')
return current_app.response_class(json.dumps(dict(*args, **kwargs),
indent=None if request.is_xhr else 2), mimetype='application/json')
content = json.dumps(dict(*args, **kwargs),
cls=current_app.json_encoder_class,
indent=None if request.is_xhr else 2)
return current_app.response_class(content, mimetype='application/json')


def make_response(*args):
Expand Down Expand Up @@ -606,6 +609,28 @@ def find_package(import_name):
return None, package_path


class JSONEncoder(json.JSONEncoder):
"""Default implementation of :class:`json.JSONEncoder` which provides
serialization for :class:`datetime.datetime` objects (to ISO 8601 format).
.. versionadded:: 0.9
"""

def default(self, obj):
"""Provides serialization for :class:`datetime.datetime` objects (in
addition to the serialization provided by the default
:class:`json.JSONEncoder` implementation).
If `obj` is a :class:`datetime.datetime` object, this converts it into
the corresponding ISO 8601 string representation.
"""
if isinstance(obj, datetime.datetime):
return obj.isoformat()
return super(JSONEncoder, self).default(obj)


class locked_cached_property(object):
"""A decorator that converts a function into a lazy property. The
function wrapped is called the first time to retrieve the result
Expand Down
33 changes: 33 additions & 0 deletions flask/testsuite/helpers.py
Expand Up @@ -11,6 +11,7 @@

from __future__ import with_statement

import datetime
import os
import flask
import unittest
Expand Down Expand Up @@ -64,6 +65,38 @@ def index():
content_type='application/json; charset=iso-8859-15')
self.assert_equal(resp.data, u'Hällo Wörld'.encode('utf-8'))

def test_jsonify_datetime(self):
app = flask.Flask(__name__)
app.testing = True
now = datetime.datetime.now()
d = dict(a=123, b=now)
@app.route('/json')
def json():
return flask.jsonify(d)
c = app.test_client()
resp = c.get('/json')
responsedict = flask.json.loads(resp.data)
self.assert_equal(responsedict, dict(a=123, b=now.isoformat()))

def test_json_encoder(self):
class MyEncoder(flask.json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime.datetime):
return 'foo'
return super(MyEncoder, self).default(obj)
app = flask.Flask(__name__)
app.testing = True
app.json_encoder_class = MyEncoder
now = datetime.datetime.now()
d = dict(a=123, b=now)
@app.route('/json')
def json():
return flask.jsonify(d)
c = app.test_client()
resp = c.get('/json')
responsedict = flask.json.loads(resp.data)
self.assert_equal(responsedict, dict(a=123, b='foo'))

def test_jsonify(self):
d = dict(a=23, b=42, c=[1, 2, 3])
app = flask.Flask(__name__)
Expand Down

0 comments on commit a7d3059

Please sign in to comment.