From cfa797c0681b7fef47cf93955fd06b54ddd09a7f Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 25 Nov 2017 17:38:20 +0200 Subject: [PATCH] bpo-24641: Improved error message for JSON unserializible keys. (#4364) Also updated an example for default() in the module docstring. Removed quotes around type name in other error messages. --- Lib/json/__init__.py | 7 ++++--- Lib/json/encoder.py | 7 ++++--- Lib/test/test_json/test_fail.py | 13 ++++++++----- Modules/_json.c | 5 +++-- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/Lib/json/__init__.py b/Lib/json/__init__.py index 6d0511ebfe9..a5660099af7 100644 --- a/Lib/json/__init__.py +++ b/Lib/json/__init__.py @@ -76,7 +76,8 @@ Specializing JSON object encoding:: >>> def encode_complex(obj): ... if isinstance(obj, complex): ... return [obj.real, obj.imag] - ... raise TypeError(repr(obj) + " is not JSON serializable") + ... raise TypeError(f'Object of type {obj.__class__.__name__} ' + ... f'is not JSON serializable') ... >>> json.dumps(2 + 1j, default=encode_complex) '[2.0, 1.0]' @@ -344,8 +345,8 @@ def loads(s, *, encoding=None, cls=None, object_hook=None, parse_float=None, s, 0) else: if not isinstance(s, (bytes, bytearray)): - raise TypeError('the JSON object must be str, bytes or bytearray, ' - 'not {!r}'.format(s.__class__.__name__)) + raise TypeError(f'the JSON object must be str, bytes or bytearray, ' + f'not {s.__class__.__name__}') s = s.decode(detect_encoding(s), 'surrogatepass') if (cls is None and object_hook is None and diff --git a/Lib/json/encoder.py b/Lib/json/encoder.py index 41a497c5da0..fb083ed61bb 100644 --- a/Lib/json/encoder.py +++ b/Lib/json/encoder.py @@ -176,8 +176,8 @@ class JSONEncoder(object): return JSONEncoder.default(self, o) """ - raise TypeError("Object of type '%s' is not JSON serializable" % - o.__class__.__name__) + raise TypeError(f'Object of type {o.__class__.__name__} ' + f'is not JSON serializable') def encode(self, o): """Return a JSON string representation of a Python data structure. @@ -373,7 +373,8 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, elif _skipkeys: continue else: - raise TypeError("key " + repr(key) + " is not a string") + raise TypeError(f'keys must be str, int, float, bool or None, ' + f'not {key.__class__.__name__}') if first: first = False else: diff --git a/Lib/test/test_json/test_fail.py b/Lib/test/test_json/test_fail.py index 79105210214..eb9064edea9 100644 --- a/Lib/test/test_json/test_fail.py +++ b/Lib/test/test_json/test_fail.py @@ -93,12 +93,15 @@ class TestFail: def test_non_string_keys_dict(self): data = {'a' : 1, (1, 2) : 2} + with self.assertRaisesRegex(TypeError, + 'keys must be str, int, float, bool or None, not tuple'): + self.dumps(data) - #This is for c encoder - self.assertRaises(TypeError, self.dumps, data) - - #This is for python encoder - self.assertRaises(TypeError, self.dumps, data, indent=True) + def test_not_serializable(self): + import sys + with self.assertRaisesRegex(TypeError, + 'Object of type module is not JSON serializable'): + self.dumps(sys) def test_truncated_input(self): test_cases = [ diff --git a/Modules/_json.c b/Modules/_json.c index 13218a6ecce..5a9464e34fb 100644 --- a/Modules/_json.c +++ b/Modules/_json.c @@ -1650,8 +1650,9 @@ encoder_listencode_dict(PyEncoderObject *s, _PyAccu *acc, continue; } else { - /* TODO: include repr of key */ - PyErr_SetString(PyExc_TypeError, "keys must be a string"); + PyErr_Format(PyExc_TypeError, + "keys must be str, int, float, bool or None, " + "not %.100s", key->ob_type->tp_name); goto bail; }