123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376 |
- # -*- coding: utf-8 -*-
- """
- flask.json
- ~~~~~~~~~~
- :copyright: 2010 Pallets
- :license: BSD-3-Clause
- """
- import codecs
- import io
- import uuid
- from datetime import date
- from datetime import datetime
- from itsdangerous import json as _json
- from jinja2 import Markup
- from werkzeug.http import http_date
- from .._compat import PY2
- from .._compat import text_type
- from ..globals import current_app
- from ..globals import request
- try:
- import dataclasses
- except ImportError:
- dataclasses = None
- # 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
- encoder by also supporting ``datetime``, ``UUID``, ``dataclasses``,
- and ``Markup`` objects.
- ``datetime`` objects are serialized as RFC 822 datetime strings.
- This is the 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 dataclasses and dataclasses.is_dataclass(o):
- return dataclasses.asdict(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 ``<script>``
- tags. It accepts the same arguments and returns a JSON string. Note that
- this is available in templates through the ``|tojson`` filter which will
- also mark the result as safe. Due to how this function escapes certain
- characters this is safe even if used outside of ``<script>`` tags.
- The following characters are escaped in strings:
- - ``<``
- - ``>``
- - ``&``
- - ``'``
- This makes it safe to embed such strings in any place in HTML with the
- notable exception of double quoted attributes. In that case single
- quote your attributes or HTML escape it in addition.
- .. versionchanged:: 0.10
- This function's return value is now always safe for HTML usage, even
- if outside of script tags or if used in XHTML. This rule does not
- hold true when using this function in HTML attributes that are double
- quoted. Always single quote attributes if you use the ``|tojson``
- filter. Alternatively use ``|tojson|forceescape``.
- """
- rv = (
- dumps(obj, **kwargs)
- .replace(u"<", u"\\u003c")
- .replace(u">", u"\\u003e")
- .replace(u"&", u"\\u0026")
- .replace(u"'", u"\\u0027")
- )
- if not _slash_escape:
- rv = rv.replace("\\/", "/")
- return rv
- def htmlsafe_dump(obj, fp, **kwargs):
- """Like :func:`htmlsafe_dumps` but writes into a file object."""
- fp.write(text_type(htmlsafe_dumps(obj, **kwargs)))
- def jsonify(*args, **kwargs):
- """This function wraps :func:`dumps` to add a few enhancements that make
- life easier. It turns the JSON output into a :class:`~flask.Response`
- object with the :mimetype:`application/json` mimetype. For convenience, it
- also converts multiple arguments into an array or multiple keyword arguments
- into a dict. This means that both ``jsonify(1,2,3)`` and
- ``jsonify([1,2,3])`` serialize to ``[1,2,3]``.
- For clarity, the JSON serialization behavior has the following differences
- from :func:`dumps`:
- 1. Single argument: Passed straight through to :func:`dumps`.
- 2. Multiple arguments: Converted to an array before being passed to
- :func:`dumps`.
- 3. Multiple keyword arguments: Converted to a dict before being passed to
- :func:`dumps`.
- 4. Both args and kwargs: Behavior undefined and will throw an exception.
- Example usage::
- from flask import jsonify
- @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
- }
- .. versionchanged:: 0.11
- Added support for serializing top-level arrays. This introduces a
- security risk in ancient browsers. See :ref:`json-security` for details.
- This function's response will be pretty printed if the
- ``JSONIFY_PRETTYPRINT_REGULAR`` config parameter is set to True or the
- Flask app is running in debug mode. Compressed (not pretty) formatting
- currently means no indents and no spaces after separators.
- .. versionadded:: 0.2
- """
- indent = None
- separators = (",", ":")
- if current_app.config["JSONIFY_PRETTYPRINT_REGULAR"] or current_app.debug:
- indent = 2
- separators = (", ", ": ")
- if args and kwargs:
- raise TypeError("jsonify() behavior undefined when passed both args and kwargs")
- elif len(args) == 1: # single args are passed directly to dumps()
- data = args[0]
- else:
- data = args or kwargs
- return current_app.response_class(
- dumps(data, indent=indent, separators=separators) + "\n",
- mimetype=current_app.config["JSONIFY_MIMETYPE"],
- )
- def tojson_filter(obj, **kwargs):
- return Markup(htmlsafe_dumps(obj, **kwargs))
|