Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Added wrapper module around simplejson/json for much simplified custo…

…mization.
  • Loading branch information...
commit b146d8277ab90cf6d43ea54113383076e4fd0318 1 parent 301e244
@mitsuhiko authored
View
3  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
-----------
View
72 docs/api.rst
@@ -312,43 +312,71 @@ Message Flashing
.. autofunction:: get_flashed_messages
-Returning JSON
---------------
+JSON Support
+------------
-.. autofunction:: jsonify
+.. 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

Something about the wording here sounds weird. Could anyone fix this?

@untitaker Collaborator
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+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::
-.. data:: json
+ try:
+ import simplejson as json
+ except ImportError:
+ import json
- 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::
+You can instead just do this::
- try:
- import simplejson as json
- except ImportError:
- import json
+ from flask import json
- You can instead just do this::
+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:
- from flask import json
+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.
- For usage examples, read the :mod:`json` documentation.
+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:
- 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
+
+ <script type=text/javascript>
+ doSomethingWith({{ user.username|tojson|safe }});
+ </script>
+
+Note that the ``|tojson`` filter escapes forward slashes properly.
+
+.. autofunction:: jsonify
- .. sourcecode:: html+jinja
+.. autofunction:: dumps
- <script type=text/javascript>
- doSomethingWith({{ user.username|tojson|safe }});
- </script>
+.. autofunction:: dump
- Note that the ``|tojson`` filter escapes forward slashes properly.
+.. autofunction:: loads
+
+.. autofunction:: load
+
+.. autoclass:: JSONEncoder
+ :members:
+
+.. autoclass:: JSONDecoder
+ :members:
Template Rendering
------------------
+.. currentmodule:: flask
+
.. autofunction:: render_template
.. autofunction:: render_template_string
View
14 flask/__init__.py
@@ -20,9 +20,8 @@
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 @@
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
View
16 flask/app.py
@@ -24,8 +24,8 @@
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 @@ def create_jinja_environment(self):
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):
View
3  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 @@ def get_headers(self, environ):
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.
View
46 flask/helpers.py
@@ -23,10 +23,6 @@
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 @@
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('<!', '<\\u0021')
-
-
# sentinel
_missing = object()
@@ -146,37 +131,6 @@ def generator():
return wrapped_g
-def jsonify(*args, **kwargs):
- """Creates a :class:`~flask.Response` with the JSON representation of
- the given arguments with an `application/json` mimetype. The arguments
- to this function are the same as to the :class:`dict` constructor.
-
- Example usage::
-
- @app.route('/_get_current_user')
- def get_current_user():
- return jsonify(username=g.user.username,
- email=g.user.email,
- id=g.user.id)
-
- This will send a JSON response like this to the browser::
-
- {
- "username": "admin",
- "email": "admin@localhost",
- "id": 42
- }
-
- This requires Python 2.6 or an installed version of simplejson. For
- security reasons only objects are supported toplevel. For more
- information about this, have a look at :ref:`json-security`.
-
- .. versionadded:: 0.2
- """
- return current_app.response_class(json.dumps(dict(*args, **kwargs),
- indent=None if request.is_xhr else 2), mimetype='application/json')
-
-
def make_response(*args):
"""Sometimes it is necessary to set additional headers in a view. Because
views do not have to return response objects but can return a value that
View
146 flask/json.py
@@ -0,0 +1,146 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.jsonimpl
+ ~~~~~~~~~~~~~~
+
+ Implementation helpers for the JSON support in Flask.
+
+ :copyright: (c) 2012 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+from datetime import datetime
+from .globals import current_app, request
+
+from werkzeug.http import http_date
+
+# Use the same json implementation as itsdangerous on which we
+# depend anyways.
+from itsdangerous import simplejson as _json
+
+
+# figure out if simplejson escapes slashes. This behavior was changed
+# from one version to another without reason.
+_slash_escape = '\\/' not in _json.dumps('/')
+
+
+__all__ = ['dump', 'dumps', 'load', 'loads', 'htmlsafe_dump',
+ 'htmlsafe_dumps', 'JSONDecoder', 'JSONEncoder',
+ 'jsonify']
+
+
+class JSONEncoder(_json.JSONEncoder):
+ """The default Flask JSON encoder. This one extends the default simplejson
+ encoder by also supporting ``datetime`` objects as well as ``Markup``
+ objects which are serialized as RFC 822 datetime strings (same as the HTTP
+ date format). In order to support more data types override the
+ :meth:`default` method.
+ """
+
+ def default(self, o):
+ """Implement this method in a subclass such that it returns a
+ serializable object for ``o``, or calls the base implementation (to
+ raise a ``TypeError``).
+
+ For example, to support arbitrary iterators, you could implement
+ default like this::
+
+ def default(self, o):
+ try:
+ iterable = iter(o)
+ except TypeError:
+ pass
+ else:
+ return list(iterable)
+ return JSONEncoder.default(self, o)
+ """
+ if isinstance(o, datetime):
+ return http_date(o)
+ if hasattr(o, '__html__'):
+ return unicode(o.__html__())
+ return _json.JSONEncoder.default(self, o)
+
+
+class JSONDecoder(_json.JSONDecoder):
+ """The default JSON decoder. This one does not change the behavior from
+ the default simplejson encoder. Consult the :mod:`json` documentation
+ for more information. This decoder is not only used for the load
+ functions of this module but also :attr:`~flask.Request`.
+ """
+
+
+def dumps(obj, **kwargs):
+ """Serialize ``obj`` to a JSON formatted ``str`` by using the application's
+ configured encoder (:attr:`~flask.Flask.json_encoder`).
+ """
+ kwargs.setdefault('cls', current_app.json_encoder)
+ return _json.dumps(obj, **kwargs)
+
+
+def dump(obj, fp, **kwargs):
+ """Like :func:`dumps` but writes into a file object."""
+ kwargs.setdefault('cls', current_app.json_encoder)
+ return _json.dump(obj, fp, **kwargs)
+
+
+def loads(s, **kwargs):
+ """Unserialize a JSON object from a string ``s`` by using the application's
+ configured decoder (:attr:`~flask.Flask.json_decoder`).
+ """
+ kwargs.setdefault('cls', current_app.json_decoder)
+ return _json.loads(s, **kwargs)
+
+
+def load(fp, **kwargs):
+ """Like :func:`loads` but reads from a file object.
+ """
+ kwargs.setdefault('cls', current_app.json_decoder)
+ return _json.load(fp, **kwargs)
+
+
+def htmlsafe_dumps(obj, **kwargs):
+ """Works exactly like :func:`dumps` but is safe for use in ``<script>``
+ 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('<!', '<\\u0021')
+
+
+def htmlsafe_dump(obj, fp, **kwargs):
+ """Like :func:`htmlsafe_dumps` but writes into a file object."""
+ fp.write(htmlsafe_dumps(obj, **kwargs))
+
+
+def jsonify(*args, **kwargs):
+ """Creates a :class:`~flask.Response` with the JSON representation of
+ the given arguments with an `application/json` mimetype. The arguments
+ to this function are the same as to the :class:`dict` constructor.
+
+ Example usage::
+
+ @app.route('/_get_current_user')
+ def get_current_user():
+ return jsonify(username=g.user.username,
+ email=g.user.email,
+ id=g.user.id)
+
+ This will send a JSON response like this to the browser::
+
+ {
+ "username": "admin",
+ "email": "admin@localhost",
+ "id": 42
+ }
+
+ This requires Python 2.6 or an installed version of simplejson. For
+ security reasons only objects are supported toplevel. For more
+ information about this, have a look at :ref:`json-security`.
+
+ .. versionadded:: 0.2
+ """
+ return current_app.response_class(dumps(dict(*args, **kwargs),
+ indent=None if request.is_xhr else 2),
+ mimetype='application/json')
View
3  flask/sessions.py
@@ -13,8 +13,7 @@
from datetime import datetime
from werkzeug.http import http_date, parse_date
from werkzeug.datastructures import CallbackDict
-from .helpers import json
-from . import Markup
+from . import Markup, json
from itsdangerous import URLSafeTimedSerializer, BadSignature
View
2  flask/wrappers.py
@@ -14,7 +14,7 @@
from .exceptions import JSONBadRequest
from .debughelpers import attach_enctype_error_multidict
-from .helpers import json
+from . import json
from .globals import _request_ctx_stack
Please sign in to comment.
Something went wrong with that request. Please try again.