# -*- coding: utf-8 -*- """ flask.json ~~~~~~~~~~ :copyright: © 2010 by the Pallets team. :license: BSD, see LICENSE for more details. """ import codecs import io import uuid from datetime import date, datetime from flask.globals import current_app, request from flask._compat import text_type, PY2 from werkzeug.http import http_date from jinja2 import Markup # Use the same json implementation as itsdangerous on which we # depend anyways. from itsdangerous import json 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'] def _wrap_reader_for_text(fp, encoding): if isinstance(fp.read(0), bytes): fp = io.TextIOWrapper(io.BufferedReader(fp), encoding) return fp def _wrap_writer_for_text(fp, encoding): try: fp.write('') except TypeError: fp = io.TextIOWrapper(fp, encoding) return fp class JSONEncoder(_json.JSONEncoder): """The default Flask JSON encoder. This one extends the default simplejson encoder by also supporting ``datetime`` objects, ``UUID`` 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 :exc:`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.utctimetuple()) if isinstance(o, date): return http_date(o.timetuple()) if isinstance(o, uuid.UUID): return str(o) if hasattr(o, '__html__'): return text_type(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 decoder. 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 _dump_arg_defaults(kwargs, app=None): """Inject default arguments for dump functions.""" if app is None: app = current_app if app: bp = app.blueprints.get(request.blueprint) if request else None kwargs.setdefault( 'cls', bp.json_encoder if bp and bp.json_encoder else app.json_encoder ) if not app.config['JSON_AS_ASCII']: kwargs.setdefault('ensure_ascii', False) kwargs.setdefault('sort_keys', app.config['JSON_SORT_KEYS']) else: kwargs.setdefault('sort_keys', True) kwargs.setdefault('cls', JSONEncoder) def _load_arg_defaults(kwargs, app=None): """Inject default arguments for load functions.""" if app is None: app = current_app if app: bp = app.blueprints.get(request.blueprint) if request else None kwargs.setdefault( 'cls', bp.json_decoder if bp and bp.json_decoder else app.json_decoder ) else: kwargs.setdefault('cls', JSONDecoder) def detect_encoding(data): """Detect which UTF codec was used to encode the given bytes. The latest JSON standard (:rfc:`8259`) suggests that only UTF-8 is accepted. Older documents allowed 8, 16, or 32. 16 and 32 can be big or little endian. Some editors or libraries may prepend a BOM. :param data: Bytes in unknown UTF encoding. :return: UTF encoding name """ head = data[:4] if head[:3] == codecs.BOM_UTF8: return 'utf-8-sig' if b'\x00' not in head: return 'utf-8' if head in (codecs.BOM_UTF32_BE, codecs.BOM_UTF32_LE): return 'utf-32' if head[:2] in (codecs.BOM_UTF16_BE, codecs.BOM_UTF16_LE): return 'utf-16' if len(head) == 4: if head[:3] == b'\x00\x00\x00': return 'utf-32-be' if head[::2] == b'\x00\x00': return 'utf-16-be' if head[1:] == b'\x00\x00\x00': return 'utf-32-le' if head[1::2] == b'\x00\x00': return 'utf-16-le' if len(head) == 2: return 'utf-16-be' if head.startswith(b'\x00') else 'utf-16-le' return 'utf-8' def dumps(obj, app=None, **kwargs): """Serialize ``obj`` to a JSON-formatted string. If there is an app context pushed, use the current app's configured encoder (:attr:`~flask.Flask.json_encoder`), or fall back to the default :class:`JSONEncoder`. Takes the same arguments as the built-in :func:`json.dumps`, and does some extra configuration based on the application. If the simplejson package is installed, it is preferred. :param obj: Object to serialize to JSON. :param app: App instance to use to configure the JSON encoder. Uses ``current_app`` if not given, and falls back to the default encoder when not in an app context. :param kwargs: Extra arguments passed to :func:`json.dumps`. .. versionchanged:: 1.0.3 ``app`` can be passed directly, rather than requiring an app context for configuration. """ _dump_arg_defaults(kwargs, app=app) encoding = kwargs.pop('encoding', None) rv = _json.dumps(obj, **kwargs) if encoding is not None and isinstance(rv, text_type): rv = rv.encode(encoding) return rv def dump(obj, fp, app=None, **kwargs): """Like :func:`dumps` but writes into a file object.""" _dump_arg_defaults(kwargs, app=app) encoding = kwargs.pop('encoding', None) if encoding is not None: fp = _wrap_writer_for_text(fp, encoding) _json.dump(obj, fp, **kwargs) def loads(s, app=None, **kwargs): """Deserialize an object from a JSON-formatted string ``s``. If there is an app context pushed, use the current app's configured decoder (:attr:`~flask.Flask.json_decoder`), or fall back to the default :class:`JSONDecoder`. Takes the same arguments as the built-in :func:`json.loads`, and does some extra configuration based on the application. If the simplejson package is installed, it is preferred. :param s: JSON string to deserialize. :param app: App instance to use to configure the JSON decoder. Uses ``current_app`` if not given, and falls back to the default encoder when not in an app context. :param kwargs: Extra arguments passed to :func:`json.dumps`. .. versionchanged:: 1.0.3 ``app`` can be passed directly, rather than requiring an app context for configuration. """ _load_arg_defaults(kwargs, app=app) if isinstance(s, bytes): encoding = kwargs.pop('encoding', None) if encoding is None: encoding = detect_encoding(s) s = s.decode(encoding) return _json.loads(s, **kwargs) def load(fp, app=None, **kwargs): """Like :func:`loads` but reads from a file object.""" _load_arg_defaults(kwargs, app=app) if not PY2: fp = _wrap_reader_for_text(fp, kwargs.pop('encoding', None) or 'utf-8') return _json.load(fp, **kwargs) def htmlsafe_dumps(obj, **kwargs): """Works exactly like :func:`dumps` but is safe for use in ``