|
1 | 1 | """Utilities to manipulate JSON objects."""
|
2 | 2 | # Copyright (c) Jupyter Development Team.
|
3 | 3 | # Distributed under the terms of the Modified BSD License.
|
| 4 | +import math |
4 | 5 | import numbers
|
5 | 6 | import re
|
| 7 | +import types |
6 | 8 | import warnings
|
7 | 9 | from binascii import b2a_base64
|
8 | 10 | from collections.abc import Iterable
|
@@ -122,3 +124,69 @@ def json_default(obj):
|
122 | 124 | return float(obj)
|
123 | 125 |
|
124 | 126 | raise TypeError("%r is not JSON serializable" % obj)
|
| 127 | + |
| 128 | + |
| 129 | +# Copy of the old ipykernel's json_clean |
| 130 | +# This is temporary, it should be removed when we deprecate support for |
| 131 | +# non-valid JSON messages |
| 132 | +def json_clean(obj): |
| 133 | + # types that are 'atomic' and ok in json as-is. |
| 134 | + atomic_ok = (str, type(None)) |
| 135 | + |
| 136 | + # containers that we need to convert into lists |
| 137 | + container_to_list = (tuple, set, types.GeneratorType) |
| 138 | + |
| 139 | + # Since bools are a subtype of Integrals, which are a subtype of Reals, |
| 140 | + # we have to check them in that order. |
| 141 | + |
| 142 | + if isinstance(obj, bool): |
| 143 | + return obj |
| 144 | + |
| 145 | + if isinstance(obj, numbers.Integral): |
| 146 | + # cast int to int, in case subclasses override __str__ (e.g. boost enum, #4598) |
| 147 | + return int(obj) |
| 148 | + |
| 149 | + if isinstance(obj, numbers.Real): |
| 150 | + # cast out-of-range floats to their reprs |
| 151 | + if math.isnan(obj) or math.isinf(obj): |
| 152 | + return repr(obj) |
| 153 | + return float(obj) |
| 154 | + |
| 155 | + if isinstance(obj, atomic_ok): |
| 156 | + return obj |
| 157 | + |
| 158 | + if isinstance(obj, bytes): |
| 159 | + # unanmbiguous binary data is base64-encoded |
| 160 | + # (this probably should have happened upstream) |
| 161 | + return b2a_base64(obj).decode('ascii') |
| 162 | + |
| 163 | + if isinstance(obj, container_to_list) or ( |
| 164 | + hasattr(obj, '__iter__') and hasattr(obj, next_attr_name) |
| 165 | + ): |
| 166 | + obj = list(obj) |
| 167 | + |
| 168 | + if isinstance(obj, list): |
| 169 | + return [json_clean(x) for x in obj] |
| 170 | + |
| 171 | + if isinstance(obj, dict): |
| 172 | + # First, validate that the dict won't lose data in conversion due to |
| 173 | + # key collisions after stringification. This can happen with keys like |
| 174 | + # True and 'true' or 1 and '1', which collide in JSON. |
| 175 | + nkeys = len(obj) |
| 176 | + nkeys_collapsed = len(set(map(str, obj))) |
| 177 | + if nkeys != nkeys_collapsed: |
| 178 | + raise ValueError( |
| 179 | + 'dict cannot be safely converted to JSON: ' |
| 180 | + 'key collision would lead to dropped values' |
| 181 | + ) |
| 182 | + # If all OK, proceed by making the new dict that will be json-safe |
| 183 | + out = {} |
| 184 | + for k, v in obj.items(): |
| 185 | + out[str(k)] = json_clean(v) |
| 186 | + return out |
| 187 | + |
| 188 | + if isinstance(obj, datetime): |
| 189 | + return obj.strftime(ISO8601) |
| 190 | + |
| 191 | + # we don't understand it, it's probably an unserializable object |
| 192 | + raise ValueError("Can't clean for JSON: %r" % obj) |
0 commit comments