1"""
2raven.utils.json
3~~~~~~~~~~~~~~~~~~~~~~~~
4
5:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
6:license: BSD, see LICENSE for more details.
7"""
8
9from __future__ import absolute_import
10
11import codecs
12import datetime
13import uuid
14import json
15
16try:
17    JSONDecodeError = json.JSONDecodeError
18except AttributeError:
19    JSONDecodeError = ValueError
20
21
22class BetterJSONEncoder(json.JSONEncoder):
23    ENCODER_BY_TYPE = {
24        uuid.UUID: lambda o: o.hex,
25        datetime.datetime: lambda o: o.strftime('%Y-%m-%dT%H:%M:%SZ'),
26        set: list,
27        frozenset: list,
28        bytes: lambda o: o.decode('utf-8', errors='replace')
29    }
30
31    def default(self, obj):
32        try:
33            encoder = self.ENCODER_BY_TYPE[type(obj)]
34        except KeyError:
35            try:
36                return super(BetterJSONEncoder, self).default(obj)
37            except TypeError:
38                return repr(obj)
39        return encoder(obj)
40
41
42def better_decoder(data):
43    return data
44
45
46def dumps(value, **kwargs):
47    try:
48        return json.dumps(value, cls=BetterJSONEncoder, **kwargs)
49    except Exception:
50        kwargs['encoding'] = 'safe-utf-8'
51        return json.dumps(value, cls=BetterJSONEncoder, **kwargs)
52
53
54def loads(value, **kwargs):
55    return json.loads(value, object_hook=better_decoder)
56
57
58_utf8_encoder = codecs.getencoder('utf-8')
59
60
61def safe_encode(input, errors='backslashreplace'):
62    return _utf8_encoder(input, errors)
63
64
65_utf8_decoder = codecs.getdecoder('utf-8')
66
67
68def safe_decode(input, errors='replace'):
69    return _utf8_decoder(input, errors)
70
71
72class Codec(codecs.Codec):
73
74    def encode(self, input, errors='backslashreplace'):
75        return safe_encode(input, errors)
76
77    def decode(self, input, errors='replace'):
78        return safe_decode(input, errors)
79
80
81class IncrementalEncoder(codecs.IncrementalEncoder):
82    def encode(self, input, final=False):
83        return safe_encode(input, self.errors)[0]
84
85
86class IncrementalDecoder(codecs.IncrementalDecoder):
87    def decode(self, input, final=False):
88        return safe_decode(input, self.errors)[0]
89
90
91class StreamWriter(Codec, codecs.StreamWriter):
92    pass
93
94
95class StreamReader(Codec, codecs.StreamReader):
96    pass
97
98
99def getregentry(name):
100    if name == 'safe-utf-8':
101        return codecs.CodecInfo(
102            name=name,
103            encode=safe_encode,
104            decode=safe_decode,
105            incrementalencoder=IncrementalEncoder,
106            incrementaldecoder=IncrementalDecoder,
107            streamreader=StreamReader,
108            streamwriter=StreamWriter,
109        )
110
111
112codecs.register(getregentry)
113