diff --git a/CHANGES b/CHANGES
index b79c6814..2a87cbe2 100644
--- a/CHANGES
+++ b/CHANGES
@@ -17,6 +17,9 @@ Release date to be decided.
- ``tojson`` filter now does not escape script blocks in HTML5 parsers.
- Flask will now raise an error if you attempt to register a new function
on an already used endpoint.
+- Added wrapper module around simplejson and added default serialization
+ of datetime objects. This allows much easier customization of how
+ JSON is handled by Flask or any Flask extension.
Version 0.9
-----------
diff --git a/docs/api.rst b/docs/api.rst
index 316c76ab..dbd1877e 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -312,43 +312,71 @@ Message Flashing
.. autofunction:: get_flashed_messages
-Returning JSON
---------------
+JSON Support
+------------
+
+.. module:: flask.json
+
+Flask uses ``simplejson`` for the JSON implementation. Since simplejson
+is provided both by the standard library as well as extension Flask will
+try simplejson first and then fall back to the stdlib json module. On top
+of that it will delegate access to the current application's JSOn encoders
+and decoders for easier customization.
+
+So for starters instead of doing::
+
+ try:
+ import simplejson as json
+ except ImportError:
+ import json
+
+You can instead just do this::
+
+ from flask import json
+
+For usage examples, read the :mod:`json` documentation in the standard
+lirbary. The following extensions are by default applied to the stdlib's
+JSON module:
+
+1. ``datetime`` objects are serialized as :rfc:`822` strings.
+2. Any object with an ``__html__`` method (like :class:`~flask.Markup`)
+ will ahve that method called and then the return value is serialized
+ as string.
+
+The :func:`~htmlsafe_dumps` function of this json module is also available
+as filter called ``|tojson`` in Jinja2. Note that inside `script`
+tags no escaping must take place, so make sure to disable escaping
+with ``|safe`` if you intend to use it inside `script` tags:
+
+.. sourcecode:: html+jinja
+
+
+
+Note that the ``|tojson`` filter escapes forward slashes properly.
.. autofunction:: jsonify
-.. data:: json
+.. autofunction:: dumps
- If JSON support is picked up, this will be the module that Flask is
- using to parse and serialize JSON. So instead of doing this yourself::
+.. autofunction:: dump
- try:
- import simplejson as json
- except ImportError:
- import json
+.. autofunction:: loads
- You can instead just do this::
+.. autofunction:: load
- from flask import json
+.. autoclass:: JSONEncoder
+ :members:
- For usage examples, read the :mod:`json` documentation.
-
- The :func:`~json.dumps` function of this json module is also available
- as filter called ``|tojson`` in Jinja2. Note that inside `script`
- tags no escaping must take place, so make sure to disable escaping
- with ``|safe`` if you intend to use it inside `script` tags:
-
- .. sourcecode:: html+jinja
-
-
-
- Note that the ``|tojson`` filter escapes forward slashes properly.
+.. autoclass:: JSONDecoder
+ :members:
Template Rendering
------------------
+.. currentmodule:: flask
+
.. autofunction:: render_template
.. autofunction:: render_template_string
diff --git a/flask/__init__.py b/flask/__init__.py
index fda94f39..6e7883fb 100644
--- a/flask/__init__.py
+++ b/flask/__init__.py
@@ -20,9 +20,8 @@ from jinja2 import Markup, escape
from .app import Flask, Request, Response
from .config import Config
-from .helpers import url_for, jsonify, flash, \
- send_file, send_from_directory, get_flashed_messages, \
- get_template_attribute, make_response, safe_join, \
+from .helpers import url_for, flash, send_file, send_from_directory, \
+ get_flashed_messages, get_template_attribute, make_response, safe_join, \
stream_with_context
from .globals import current_app, g, request, session, _request_ctx_stack, \
_app_ctx_stack
@@ -37,8 +36,13 @@ from .signals import signals_available, template_rendered, request_started, \
request_finished, got_request_exception, request_tearing_down, \
appcontext_tearing_down
-# only import json if it's available
-from .helpers import json
+# We're not exposing the actual json module but a convenient wrapper around
+# it.
+from . import json
+
+# This was the only thing that flask used to export at one point and it had
+# a more generic name.
+jsonify = json.jsonify
# backwards compat, goes away in 1.0
from .sessions import SecureCookieSession as Session
diff --git a/flask/app.py b/flask/app.py
index 327cb8cf..1763aa74 100644
--- a/flask/app.py
+++ b/flask/app.py
@@ -24,8 +24,8 @@ from werkzeug.exceptions import HTTPException, InternalServerError, \
MethodNotAllowed, BadRequest
from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \
- locked_cached_property, _tojson_filter, _endpoint_from_view_func, \
- find_package
+ locked_cached_property, _endpoint_from_view_func, find_package
+from . import json
from .wrappers import Request, Response
from .config import ConfigAttribute, Config
from .ctx import RequestContext, AppContext, _RequestGlobals
@@ -238,6 +238,16 @@ class Flask(_PackageBoundObject):
'-' * 80
)
+ #: The JSON encoder class to use. Defaults to :class:`~flask.json.JSONEncoder`.
+ #:
+ #: .. versionadded:: 0.10
+ json_encoder = json.JSONEncoder
+
+ #: The JSON decoder class to use. Defaults to :class:`~flask.json.JSONDecoder`.
+ #:
+ #: .. versionadded:: 0.10
+ json_decoder = json.JSONDecoder
+
#: Options that are passed directly to the Jinja2 environment.
jinja_options = ImmutableDict(
extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_']
@@ -637,7 +647,7 @@ class Flask(_PackageBoundObject):
url_for=url_for,
get_flashed_messages=get_flashed_messages
)
- rv.filters['tojson'] = _tojson_filter
+ rv.filters['tojson'] = json.htmlsafe_dumps
return rv
def create_global_jinja_loader(self):
diff --git a/flask/exceptions.py b/flask/exceptions.py
index 9ccdedab..83b9556b 100644
--- a/flask/exceptions.py
+++ b/flask/exceptions.py
@@ -9,7 +9,7 @@
:license: BSD, see LICENSE for more details.
"""
from werkzeug.exceptions import HTTPException, BadRequest
-from .helpers import json
+from . import json
class JSONHTTPException(HTTPException):
@@ -39,7 +39,6 @@ class JSONHTTPException(HTTPException):
class JSONBadRequest(JSONHTTPException, BadRequest):
"""Represents an HTTP ``400 Bad Request`` error whose body contains an
error message in JSON format instead of HTML format (as in the superclass).
-
"""
#: The description of the error which occurred as a string.
diff --git a/flask/helpers.py b/flask/helpers.py
index 8780ee20..3f06c116 100644
--- a/flask/helpers.py
+++ b/flask/helpers.py
@@ -23,10 +23,6 @@ from werkzeug.routing import BuildError
from werkzeug.urls import url_quote
from functools import update_wrapper
-# Use the same json implementation as itsdangerous on which we
-# depend anyways.
-from itsdangerous import simplejson as json
-
from werkzeug.datastructures import Headers
from werkzeug.exceptions import NotFound
@@ -43,17 +39,6 @@ from .globals import session, _request_ctx_stack, _app_ctx_stack, \
current_app, request
-# figure out if simplejson escapes slashes. This behavior was changed
-# from one version to another without reason.
-_slash_escape = '\\/' not in json.dumps('/')
-
-def _tojson_filter(*args, **kwargs):
- rv = json.dumps(*args, **kwargs)
- if _slash_escape:
- rv = rv.replace('/', '\\/')
- return rv.replace('``
+ tags. It accepts the same arguments and returns a JSON string. Note that
+ this is available in templates through the ``|tojson`` filter but it will
+ have to be wrapped in ``|safe`` unless **true** XHTML is being used.
+ """
+ rv = dumps(obj, **kwargs)
+ if _slash_escape:
+ rv = rv.replace('/', '\\/')
+ return rv.replace('