diff --git a/Lib/json/__init__.py b/Lib/json/__init__.py index b8d5e6cff8cb21..c951c30df58911 100644 --- a/Lib/json/__init__.py +++ b/Lib/json/__init__.py @@ -242,12 +242,13 @@ def dumps(obj, *, skipkeys=False, ensure_ascii=True, check_circular=True, def detect_encoding(b): - bstartswith = b.startswith - if bstartswith((codecs.BOM_UTF32_BE, codecs.BOM_UTF32_LE)): - return 'utf-32' - if bstartswith((codecs.BOM_UTF16_BE, codecs.BOM_UTF16_LE)): - return 'utf-16' - if bstartswith(codecs.BOM_UTF8): + for prefix in (codecs.BOM_UTF32_BE, codecs.BOM_UTF32_LE): + if b[:len(prefix)] == prefix: + return 'utf-32' + for prefix in (codecs.BOM_UTF16_BE, codecs.BOM_UTF16_LE): + if b[:len(prefix)] == prefix: + return 'utf-16' + if b[:len(codecs.BOM_UTF8)] == codecs.BOM_UTF8: return 'utf-8-sig' if len(b) >= 4: @@ -343,10 +344,14 @@ def loads(s, *, encoding=None, cls=None, object_hook=None, parse_float=None, raise JSONDecodeError("Unexpected UTF-8 BOM (decode using utf-8-sig)", 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__)) - s = s.decode(detect_encoding(s), 'surrogatepass') + if not isinstance(s, (bytes, bytearray, memoryview)): + try: + s = memoryview(s) + except TypeError: + raise TypeError('the JSON object must be str, bytes or ' + 'bytearray or memoryview compatible object, ' + 'not {!r}'.format(s.__class__.__name__)) + s = str(s, detect_encoding(s), 'surrogatepass') if (cls is None and object_hook is None and parse_int is None and parse_float is None and diff --git a/Lib/test/test_json/test_decode.py b/Lib/test/test_json/test_decode.py index d84ef7da24463b..31243d77388ddb 100644 --- a/Lib/test/test_json/test_decode.py +++ b/Lib/test/test_json/test_decode.py @@ -1,3 +1,4 @@ +import array import decimal from io import StringIO from collections import OrderedDict @@ -20,6 +21,15 @@ def test_empty_objects(self): self.assertEqual(self.loads('[]'), []) self.assertEqual(self.loads('""'), "") + def test_memoryview(self): + data = memoryview(b'{"key": "val"}') + self.assertEqual(self.loads(data), {"key": "val"}) + + def test_buffer(self): + data = array.array('B') + data.frombytes(b'{"key": "val"}') + self.assertEqual(self.loads(data), {"key": "val"}) + def test_object_pairs_hook(self): s = '{"xkd":1, "kcw":2, "art":3, "hxm":4, "qrt":5, "pad":6, "hoy":7}' p = [("xkd", 1), ("kcw", 2), ("art", 3), ("hxm", 4), diff --git a/Misc/NEWS b/Misc/NEWS index 71db0ee46b0dc4..b0f5feeb3f467f 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -317,6 +317,8 @@ Extension Modules Library ------- +- bpo-30193: Allow to load buffer objects with ``json.loads()`` + - bpo-30101: Add support for curses.A_ITALIC. - bpo-29822: inspect.isabstract() now works during __init_subclass__. Patch