1"""
2Functions to work with JSON
3"""
4
5
6import json
7import logging
8
9import salt.utils.data
10import salt.utils.stringutils
11
12log = logging.getLogger(__name__)
13
14
15# One to one mappings
16JSONEncoder = json.JSONEncoder
17
18
19def __split(raw):
20    """
21    Performs a splitlines on the string. This function exists to make mocking
22    possible in unit tests, since the member functions of the str/unicode
23    builtins cannot be mocked.
24    """
25    return raw.splitlines()
26
27
28def find_json(raw):
29    """
30    Pass in a raw string and load the json when it starts. This allows for a
31    string to start with garbage and end with json but be cleanly loaded
32    """
33    ret = {}
34    lines = __split(raw)
35    for ind, _ in enumerate(lines):
36        try:
37            working = "\n".join(lines[ind:])
38        except UnicodeDecodeError:
39            working = "\n".join(salt.utils.data.decode(lines[ind:]))
40
41        try:
42            ret = json.loads(working)
43        except ValueError:
44            continue
45        if ret:
46            return ret
47    if not ret:
48        # Not json, raise an error
49        raise ValueError
50
51
52def import_json():
53    """
54    Import a json module, starting with the quick ones and going down the list)
55    """
56    for fast_json in ("ujson", "yajl", "json"):
57        try:
58            mod = __import__(fast_json)
59            log.trace("loaded %s json lib", fast_json)
60            return mod
61        except ImportError:
62            continue
63
64
65def load(fp, **kwargs):
66    """
67    .. versionadded:: 2018.3.0
68
69    Wraps json.load
70
71    You can pass an alternate json module (loaded via import_json() above)
72    using the _json_module argument)
73    """
74    return kwargs.pop("_json_module", json).load(fp, **kwargs)
75
76
77def loads(s, **kwargs):
78    """
79    .. versionadded:: 2018.3.0
80
81    Wraps json.loads and prevents a traceback in the event that a bytestring is
82    passed to the function. (Python < 3.6 cannot load bytestrings)
83
84    You can pass an alternate json module (loaded via import_json() above)
85    using the _json_module argument)
86    """
87    json_module = kwargs.pop("_json_module", json)
88    try:
89        return json_module.loads(s, **kwargs)
90    except TypeError as exc:
91        # json.loads cannot load bytestrings in Python < 3.6
92        if isinstance(s, bytes):
93            return json_module.loads(salt.utils.stringutils.to_unicode(s), **kwargs)
94        else:
95            raise
96
97
98def dump(obj, fp, **kwargs):
99    """
100    .. versionadded:: 2018.3.0
101
102    Wraps json.dump, and assumes that ensure_ascii is False (unless explicitly
103    passed as True) for unicode compatibility. Note that setting it to True
104    will mess up any unicode characters, as they will be dumped as the string
105    literal version of the unicode code point.
106
107    On Python 2, encodes the result to a str since json.dump does not want
108    unicode types.
109
110    You can pass an alternate json module (loaded via import_json() above)
111    using the _json_module argument)
112    """
113    json_module = kwargs.pop("_json_module", json)
114    if "ensure_ascii" not in kwargs:
115        kwargs["ensure_ascii"] = False
116    return json_module.dump(obj, fp, **kwargs)
117
118
119def dumps(obj, **kwargs):
120    """
121    .. versionadded:: 2018.3.0
122
123    Wraps json.dumps, and assumes that ensure_ascii is False (unless explicitly
124    passed as True) for unicode compatibility. Note that setting it to True
125    will mess up any unicode characters, as they will be dumped as the string
126    literal version of the unicode code point.
127
128    On Python 2, encodes the result to a str since json.dumps does not want
129    unicode types.
130
131    You can pass an alternate json module (loaded via import_json() above)
132    using the _json_module argument)
133    """
134    json_module = kwargs.pop("_json_module", json)
135    if "ensure_ascii" not in kwargs:
136        kwargs["ensure_ascii"] = False
137    return json_module.dumps(obj, **kwargs)
138