1# -*- coding: utf-8 -*-
2"""
3    webapp2_extras.json
4    ===================
5
6    JSON helpers for webapp2.
7
8    :copyright: 2011 by tipfy.org.
9    :license: Apache Sotware License, see LICENSE for details.
10"""
11from __future__ import absolute_import
12
13import base64
14import urllib
15
16try:
17    # Preference for installed library with updated fixes.
18    # Also available in Google App Engine SDK >= 1.4.2.
19    import simplejson as json
20except ImportError: # pragma: no cover
21    try:
22        # Standard library module in Python >= 2.6.
23        import json
24    except ImportError: # pragma: no cover
25        raise RuntimeError(
26            'A JSON parser is required, e.g., simplejson at '
27            'http://pypi.python.org/pypi/simplejson/')
28
29assert hasattr(json, 'loads') and hasattr(json, 'dumps'), \
30    'Expected a JSON module with the functions loads() and dumps().'
31
32
33def encode(value, *args, **kwargs):
34    """Serializes a value to JSON.
35
36    This comes from `Tornado`_.
37
38    :param value:
39        A value to be serialized.
40    :param args:
41        Extra arguments to be passed to `json.dumps()`.
42    :param kwargs:
43        Extra keyword arguments to be passed to `json.dumps()`.
44    :returns:
45        The serialized value.
46    """
47    # By default encode using a compact format.
48    kwargs.setdefault('separators', (',', ':'))
49    # JSON permits but does not require forward slashes to be escaped.
50    # This is useful when json data is emitted in a <script> tag
51    # in HTML, as it prevents </script> tags from prematurely terminating
52    # the javascript.  Some json libraries do this escaping by default,
53    # although python's standard library does not, so we do it here.
54    # See: http://goo.gl/WsXwv
55    return json.dumps(value, *args, **kwargs).replace("</", "<\\/")
56
57
58def decode(value, *args, **kwargs):
59    """Deserializes a value from JSON.
60
61    This comes from `Tornado`_.
62
63    :param value:
64        A value to be deserialized.
65    :param args:
66        Extra arguments to be passed to `json.loads()`.
67    :param kwargs:
68        Extra keyword arguments to be passed to `json.loads()`.
69    :returns:
70        The deserialized value.
71    """
72    if isinstance(value, str):
73        value = value.decode('utf-8')
74
75    assert isinstance(value, unicode)
76    return json.loads(value, *args, **kwargs)
77
78
79def b64encode(value, *args, **kwargs):
80    """Serializes a value to JSON and encodes it using base64.
81
82    Parameters and return value are the same from :func:`encode`.
83    """
84    return base64.b64encode(encode(value, *args, **kwargs))
85
86
87def b64decode(value, *args, **kwargs):
88    """Decodes a value using base64 and deserializes it from JSON.
89
90    Parameters and return value are the same from :func:`decode`.
91    """
92    return decode(base64.b64decode(value), *args, **kwargs)
93
94
95def quote(value, *args, **kwargs):
96    """Serializes a value to JSON and encodes it using urllib.quote.
97
98    Parameters and return value are the same from :func:`encode`.
99    """
100    return urllib.quote(encode(value, *args, **kwargs))
101
102
103def unquote(value, *args, **kwargs):
104    """Decodes a value using urllib.unquote and deserializes it from JSON.
105
106    Parameters and return value are the same from :func:`decode`.
107    """
108    return decode(urllib.unquote(value), *args, **kwargs)
109