diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index 8408255..6842e7a 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -954,6 +954,9 @@ class ChainMap(MutableMapping): 'Clear maps[0], leaving maps[1:] intact.' self.maps[0].clear() + def __json__(self): + return OrderedDict(self) + ################################################################################ ### UserDict @@ -1020,6 +1023,9 @@ class UserDict(MutableMapping): d[key] = value return d + def __json__(self): + return self.data + ################################################################################ @@ -1093,6 +1099,9 @@ class UserList(MutableSequence): else: self.data.extend(other) + def __json__(self): + return self.data + ################################################################################ @@ -1243,3 +1252,6 @@ class UserString(Sequence): return self.__class__(self.data.translate(*args)) def upper(self): return self.__class__(self.data.upper()) def zfill(self, width): return self.__class__(self.data.zfill(width)) + + def __json__(self): + return self.data diff --git a/Lib/json/__init__.py b/Lib/json/__init__.py index b8d5e6c..5d585a1 100644 --- a/Lib/json/__init__.py +++ b/Lib/json/__init__.py @@ -99,12 +99,13 @@ __version__ = '2.0.9' __all__ = [ 'dump', 'dumps', 'load', 'loads', 'JSONDecoder', 'JSONDecodeError', 'JSONEncoder', + 'register', ] __author__ = 'Bob Ippolito ' from .decoder import JSONDecoder, JSONDecodeError -from .encoder import JSONEncoder +from .encoder import JSONEncoder, dispatch_table import codecs _default_encoder = JSONEncoder( @@ -365,3 +366,7 @@ def loads(s, *, encoding=None, cls=None, object_hook=None, parse_float=None, if parse_constant is not None: kw['parse_constant'] = parse_constant return cls(**kw).decode(s) + + +def register(cls, func): + dispatch_table[cls] = func diff --git a/Lib/json/encoder.py b/Lib/json/encoder.py index 41a497c..f37e103 100644 --- a/Lib/json/encoder.py +++ b/Lib/json/encoder.py @@ -33,6 +33,8 @@ for i in range(0x20): INFINITY = float('inf') +dispatch_table = {} + def py_encode_basestring(s): """Return a JSON representation of a Python string @@ -176,8 +178,17 @@ class JSONEncoder(object): return JSONEncoder.default(self, o) """ + cls = type(o) + asjson = getattr(self, 'dispatch_table', dispatch_table).get(cls) + #print(id(dispatch_table)) + #print(getattr(self, 'dispatch_table', dispatch_table)) + if asjson is not None: + return asjson(o) + asjson = getattr(o, '__json__', None) + if asjson is not None: + return asjson() raise TypeError("Object of type '%s' is not JSON serializable" % - o.__class__.__name__) + cls.__name__) def encode(self, o): """Return a JSON string representation of a Python data structure. @@ -428,6 +439,8 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, yield from _iterencode_list(o, _current_indent_level) elif isinstance(o, dict): yield from _iterencode_dict(o, _current_indent_level) + elif isinstance(o, JSONRepr): + yield str(o) else: if markers is not None: markerid = id(o) @@ -439,3 +452,16 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, if markers is not None: del markers[markerid] return _iterencode + +class JSONRepr: + def __init__(self, json): + self.json = json + def __str__(self): + return self.json + +if 1: + # Should be implemented as __json__ methods + import decimal + def decimal_as_json(o): + return JSONRepr(str(o)) + dispatch_table[decimal.Decimal] = decimal_as_json diff --git a/Lib/test/test_json/test_default.py b/Lib/test/test_json/test_default.py index 9b8325e..d6ac542 100644 --- a/Lib/test/test_json/test_default.py +++ b/Lib/test/test_json/test_default.py @@ -1,3 +1,9 @@ +import json +import array +import collections +import decimal +import types +from collections import OrderedDict from test.test_json import PyTest, CTest @@ -7,6 +13,83 @@ class TestDefault: self.dumps(type, default=repr), self.dumps(repr(type))) + def test_ordereddict(self): + self.assertEqual( + self.dumps(collections.OrderedDict(a=1, b=2)), + '{"a": 1, "b": 2}') + + def test_deque(self): + self.assertEqual( + self.dumps(collections.deque([1, 2, 3])), + '[1, 2, 3]') + self.assertEqual( + self.dumps(collections.deque([1, 2, 3], maxlen=2)), + '[2, 3]') + + def test_chainmap(self): + self.assertEqual( + self.dumps(collections.ChainMap({'a': 1}, {'a': 2})), + '{"a": 1}') + self.assertEqual( + self.dumps(collections.ChainMap({}, {'a': 2})), + '{"a": 2}') + + def test_userdict(self): + self.assertEqual( + self.dumps(collections.UserDict({'a': 1})), + '{"a": 1}') + + def test_userlist(self): + self.assertEqual( + self.dumps(collections.UserList([1, 2, 3])), + '[1, 2, 3]') + + def test_userstring(self): + self.assertEqual( + self.dumps(collections.UserString('abc')), + '"abc"') + + def test_mappingproxy(self): + self.assertEqual( + self.dumps(types.MappingProxyType(collections.OrderedDict(a=1, b=2))), + '{"a": 1, "b": 2}') + + def test_array(self): + self.assertEqual( + self.dumps(array.array('I', [1, 2, 3])), + '[1, 2, 3]') + + def test_decimal(self): + self.assertEqual( + self.dumps(decimal.Decimal('0.12345678901234567890')), + '0.12345678901234567890') + + def test_global_dispatch_table(self): + from json.encoder import dispatch_table + class A: pass + a = A() + self.assertRaises(TypeError, json.dumps, a) + dispatch_table[A] = repr + try: + self.assertEqual(json.dumps(a), json.dumps(repr(a))) + finally: + del dispatch_table[A] + self.assertRaises(TypeError, json.dumps, a) + + def test_encoder_dispatch_table(self): + class A: pass + class Encoder(json.JSONEncoder): + dispatch_table = {A: repr} + a = A() + encoder = Encoder() + self.assertEqual(encoder.encode(a), encoder.encode(repr(a))) + + def test_json_method(self): + class A: + def __json__(self): + return repr(self) + a = A() + self.assertEqual(self.dumps(a), self.dumps(repr(a))) class TestPyDefault(TestDefault, PyTest): pass class TestCDefault(TestDefault, CTest): pass diff --git a/Modules/_collectionsmodule.c b/Modules/_collectionsmodule.c index 44e9e11..797f01a 100644 --- a/Modules/_collectionsmodule.c +++ b/Modules/_collectionsmodule.c @@ -1544,6 +1544,12 @@ deque_sizeof(dequeobject *deque, void *unused) PyDoc_STRVAR(sizeof_doc, "D.__sizeof__() -- size of D in memory, in bytes"); +static PyObject * +deque_json(PyObject *deque, PyObject *unused) +{ + return PySequence_List(deque); +} + static int deque_bool(dequeobject *deque) { @@ -1636,6 +1642,8 @@ static PyMethodDef deque_methods[] = { METH_FASTCALL, rotate_doc}, {"__sizeof__", (PyCFunction)deque_sizeof, METH_NOARGS, sizeof_doc}, + {"__json__", deque_json, + METH_NOARGS, NULL}, {NULL, NULL} /* sentinel */ }; diff --git a/Modules/arraymodule.c b/Modules/arraymodule.c index 464820d..99156b4 100644 --- a/Modules/arraymodule.c +++ b/Modules/arraymodule.c @@ -1704,6 +1704,18 @@ array_array___sizeof___impl(arrayobject *self) } +/*[clinic input] +array.array.__json__ +[clinic start generated code]*/ + +static PyObject * +array_array___json___impl(arrayobject *self) +/*[clinic end generated code: output=4fd19502af01150b input=467eb79b26564e18]*/ +{ + return PySequence_List((PyObject *)self); +} + + /*********************** Pickling support ************************/ static const struct mformatdescr { @@ -2227,6 +2239,7 @@ static PyMethodDef array_methods[] = { ARRAY_ARRAY_TOBYTES_METHODDEF ARRAY_ARRAY_TOUNICODE_METHODDEF ARRAY_ARRAY___SIZEOF___METHODDEF + ARRAY_ARRAY___JSON___METHODDEF {NULL, NULL} /* sentinel */ }; diff --git a/Modules/clinic/arraymodule.c.h b/Modules/clinic/arraymodule.c.h index 2a67674..9e05ef1 100644 --- a/Modules/clinic/arraymodule.c.h +++ b/Modules/clinic/arraymodule.c.h @@ -447,6 +447,23 @@ array_array___sizeof__(arrayobject *self, PyObject *Py_UNUSED(ignored)) return array_array___sizeof___impl(self); } +PyDoc_STRVAR(array_array___json____doc__, +"__json__($self, /)\n" +"--\n" +"\n"); + +#define ARRAY_ARRAY___JSON___METHODDEF \ + {"__json__", (PyCFunction)array_array___json__, METH_NOARGS, array_array___json____doc__}, + +static PyObject * +array_array___json___impl(arrayobject *self); + +static PyObject * +array_array___json__(arrayobject *self, PyObject *Py_UNUSED(ignored)) +{ + return array_array___json___impl(self); +} + PyDoc_STRVAR(array__array_reconstructor__doc__, "_array_reconstructor($module, arraytype, typecode, mformat_code, items,\n" " /)\n" @@ -521,4 +538,4 @@ PyDoc_STRVAR(array_arrayiterator___setstate____doc__, #define ARRAY_ARRAYITERATOR___SETSTATE___METHODDEF \ {"__setstate__", (PyCFunction)array_arrayiterator___setstate__, METH_O, array_arrayiterator___setstate____doc__}, -/*[clinic end generated code: output=d186a7553c1f1a41 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=fe17aed6b96eb7c2 input=a9049054013a1b77]*/ diff --git a/Objects/descrobject.c b/Objects/descrobject.c index 20c0d36..e42aca1 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -885,6 +885,8 @@ static PyMethodDef mappingproxy_methods[] = { PyDoc_STR("D.items() -> list of D's (key, value) pairs, as 2-tuples")}, {"copy", (PyCFunction)mappingproxy_copy, METH_NOARGS, PyDoc_STR("D.copy() -> a shallow copy of D")}, + {"__json__", (PyCFunction)mappingproxy_copy, METH_NOARGS, + NULL}, {0} };