diff -r 2b3b873e2b19 Lib/json/encoder.py --- a/Lib/json/encoder.py Tue Jul 30 12:24:25 2013 -0700 +++ b/Lib/json/encoder.py Sun Aug 04 10:46:57 2013 -0700 @@ -291,23 +291,25 @@ def _make_iterencode(markers, _default, buf = separator if isinstance(value, str): yield buf + _encoder(value) elif value is None: yield buf + 'null' elif value is True: yield buf + 'true' elif value is False: yield buf + 'false' elif isinstance(value, int): - yield buf + str(value) + # coerce int subclasses to int (primarily for Enum) + yield buf + str(int(value)) elif isinstance(value, float): - yield buf + _floatstr(value) + # coerce float subclasses to float (primarily for Enum) + yield buf + _floatstr(float(value)) else: yield buf if isinstance(value, (list, tuple)): chunks = _iterencode_list(value, _current_indent_level) elif isinstance(value, dict): chunks = _iterencode_dict(value, _current_indent_level) else: chunks = _iterencode(value, _current_indent_level) yield from chunks if newline_indent is not None: @@ -339,51 +341,55 @@ def _make_iterencode(markers, _default, if _sort_keys: items = sorted(dct.items(), key=lambda kv: kv[0]) else: items = dct.items() for key, value in items: if isinstance(key, str): pass # JavaScript is weakly typed for these, so it makes sense to # also allow them. Many encoders seem to do something like this. elif isinstance(key, float): - key = _floatstr(key) + # coerce float subclasses to float (primarily for Enum) + key = _floatstr(float(key)) elif key is True: key = 'true' elif key is False: key = 'false' elif key is None: key = 'null' elif isinstance(key, int): - key = str(key) + # coerce int subclasses to int (primarily for Enum) + key = str(int(key)) elif _skipkeys: continue else: raise TypeError("key " + repr(key) + " is not a string") if first: first = False else: yield item_separator yield _encoder(key) yield _key_separator if isinstance(value, str): yield _encoder(value) elif value is None: yield 'null' elif value is True: yield 'true' elif value is False: yield 'false' elif isinstance(value, int): - yield str(value) + # coerce int subclasses to int (primarily for Enum) + yield str(int(value)) elif isinstance(value, float): - yield _floatstr(value) + # coerce float subclasses to float (primarily for Enum) + yield _floatstr(float(value)) else: if isinstance(value, (list, tuple)): chunks = _iterencode_list(value, _current_indent_level) elif isinstance(value, dict): chunks = _iterencode_dict(value, _current_indent_level) else: chunks = _iterencode(value, _current_indent_level) yield from chunks if newline_indent is not None: _current_indent_level -= 1 @@ -395,23 +401,25 @@ def _make_iterencode(markers, _default, def _iterencode(o, _current_indent_level): if isinstance(o, str): yield _encoder(o) elif o is None: yield 'null' elif o is True: yield 'true' elif o is False: yield 'false' elif isinstance(o, int): - yield str(o) + # coerce int subclasses to int (primarily for Enum) + yield str(int(o)) elif isinstance(o, float): - yield _floatstr(o) + # coerce float subclasses to float (primarily for Enum) + yield _floatstr(float(o)) elif isinstance(o, (list, tuple)): yield from _iterencode_list(o, _current_indent_level) elif isinstance(o, dict): yield from _iterencode_dict(o, _current_indent_level) else: if markers is not None: markerid = id(o) if markerid in markers: raise ValueError("Circular reference detected") markers[markerid] = o diff -r 2b3b873e2b19 Lib/test/json_tests/test_enum.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/test/json_tests/test_enum.py Sun Aug 04 10:46:57 2013 -0700 @@ -0,0 +1,45 @@ +from enum import IntEnum +from test.json_tests import PyTest, CTest + + +class BigNum(IntEnum): + small = 1 + big = 1<<32 + huge = 1<<64 + really_huge = 1<<96 + +SMALL = 1 +BIG = 1<<32 +HUGE = 1<<64 +REALLY_HUGE = 1<<96 + +class TestEnum: + + def test_ints(self): + for enum in BigNum: + self.assertEqual(self.dumps(enum), str(enum.value)) + self.assertEqual(int(self.dumps(enum)), enum) + self.assertEqual(self.loads(self.dumps(enum)), enum) + + def test_list(self): + self.assertEqual(self.dumps(list(BigNum)), str([SMALL, BIG, HUGE, REALLY_HUGE])) + + def test_dict_keys(self): + s, b, h, r = BigNum + d = {s:'tiny', b:'large', h:'larger', r:'largest'} + nd = self.loads(self.dumps(d)) + self.assertEqual(nd[str(SMALL)], 'tiny') + self.assertEqual(nd[str(BIG)], 'large') + self.assertEqual(nd[str(HUGE)], 'larger') + self.assertEqual(nd[str(REALLY_HUGE)], 'largest') + + def test_dict_values(self): + d = dict(tiny=BigNum.small, large=BigNum.big, larger=BigNum.huge, largest=BigNum.really_huge) + nd = self.loads(self.dumps(d)) + self.assertEqual(nd['tiny'], SMALL) + self.assertEqual(nd['large'], BIG) + self.assertEqual(nd['larger'], HUGE) + self.assertEqual(nd['largest'], REALLY_HUGE) + +class TestPyEnum(TestEnum, PyTest): pass +class TestCEnum(TestEnum, CTest): pass diff -r 2b3b873e2b19 Modules/_json.c --- a/Modules/_json.c Tue Jul 30 12:24:25 2013 -0700 +++ b/Modules/_json.c Sun Aug 04 10:46:57 2013 -0700 @@ -109,20 +109,22 @@ static int encoder_listencode_obj(PyEncoderObject *s, _PyAccu *acc, PyObject *obj, Py_ssize_t indent_level); static int encoder_listencode_dict(PyEncoderObject *s, _PyAccu *acc, PyObject *dct, Py_ssize_t indent_level); static PyObject * _encoded_const(PyObject *obj); static void raise_errmsg(char *msg, PyObject *s, Py_ssize_t end); static PyObject * encoder_encode_string(PyEncoderObject *s, PyObject *obj); static PyObject * +encoder_encode_long(PyEncoderObject *s, PyObject *obj); +static PyObject * encoder_encode_float(PyEncoderObject *s, PyObject *obj); #define S_CHAR(c) (c >= ' ' && c <= '~' && c != '\\' && c != '"') #define IS_WHITESPACE(c) (((c) == ' ') || ((c) == '\t') || ((c) == '\n') || ((c) == '\r')) static Py_ssize_t ascii_escape_unichar(Py_UCS4 c, unsigned char *output, Py_ssize_t chars) { /* Escape unicode code point c to ASCII escape sequences in char *output. output must have at least 12 bytes unused to @@ -1295,41 +1297,76 @@ static PyObject * Py_INCREF(s_false); return s_false; } else { PyErr_SetString(PyExc_ValueError, "not a const"); return NULL; } } static PyObject * +encoder_encode_long(PyEncoderObject *s, PyObject *obj) +{ + /* Return the JSON representation of a PyLong */ + PyObject *encoded, *longobj; + if (PyLong_CheckExact(obj)) { + encoded = PyObject_Str(obj); + } + else { + longobj = PyNumber_Long(obj); + if (longobj == NULL) { + PyErr_SetString(PyExc_ValueError, "Unable to coerce int subclass to int"); + return NULL; + } + encoded = PyObject_Str(longobj); + Py_DECREF(longobj); + } + return encoded; +} + + +static PyObject * encoder_encode_float(PyEncoderObject *s, PyObject *obj) { /* Return the JSON representation of a PyFloat */ + PyObject *encoded, *floatobj; double i = PyFloat_AS_DOUBLE(obj); if (!Py_IS_FINITE(i)) { if (!s->allow_nan) { PyErr_SetString(PyExc_ValueError, "Out of range float values are not JSON compliant"); return NULL; } if (i > 0) { return PyUnicode_FromString("Infinity"); } else if (i < 0) { return PyUnicode_FromString("-Infinity"); } else { return PyUnicode_FromString("NaN"); } } - /* Use a better float format here? */ - return PyObject_Repr(obj); + /* coerce float subclasses to float (primarily for Enum) */ + if (PyFloat_CheckExact(obj)) { + /* Use a better float format here? */ + encoded = PyObject_Repr(obj); + } + else { + floatobj = PyNumber_Float(obj); + if (floatobj == NULL) { + PyErr_SetString(PyExc_ValueError, "Unable to coerce float subclass to float"); + return NULL; + } + encoded = PyObject_Repr(floatobj); + Py_DECREF(floatobj); + } + return encoded; } static PyObject * encoder_encode_string(PyEncoderObject *s, PyObject *obj) { /* Return the JSON representation of a string */ if (s->fast_encode) return py_encode_basestring_ascii(NULL, obj); else return PyObject_CallFunctionObjArgs(s->encoder, obj, NULL); @@ -1359,21 +1396,21 @@ encoder_listencode_obj(PyEncoderObject * return _steal_accumulate(acc, cstr); } else if (PyUnicode_Check(obj)) { PyObject *encoded = encoder_encode_string(s, obj); if (encoded == NULL) return -1; return _steal_accumulate(acc, encoded); } else if (PyLong_Check(obj)) { - PyObject *encoded = PyObject_Str(obj); + PyObject *encoded = encoder_encode_long(s, obj); if (encoded == NULL) return -1; return _steal_accumulate(acc, encoded); } else if (PyFloat_Check(obj)) { PyObject *encoded = encoder_encode_float(s, obj); if (encoded == NULL) return -1; return _steal_accumulate(acc, encoded); } @@ -1544,23 +1581,24 @@ encoder_listencode_dict(PyEncoderObject goto bail; } else if (key == Py_True || key == Py_False || key == Py_None) { /* This must come before the PyLong_Check because True and False are also 1 and 0.*/ kstr = _encoded_const(key); if (kstr == NULL) goto bail; } else if (PyLong_Check(key)) { - kstr = PyObject_Str(key); - if (kstr == NULL) + kstr = encoder_encode_long(s, key); + if (kstr == NULL) { goto bail; + } } else if (skipkeys) { Py_DECREF(item); continue; } else { /* TODO: include repr of key */ PyErr_SetString(PyExc_TypeError, "keys must be a string"); goto bail; }