diff --git a/README.rst b/README.rst index c84d66d..fdfc87d 100644 --- a/README.rst +++ b/README.rst @@ -110,23 +110,23 @@ Serialization +-----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+ | serialize | ``dumps()``\ [1]_ | ``Encoder()``\ [2]_ | ``dumps(n)``\ [3]_ | ``Encoder(n)``\ [4]_ | ujson\ [5]_ | simplejson\ [6]_ | stdlib\ [7]_ | yajl\ [8]_ | +=======================+======================+======================+======================+======================+======================+======================+======================+======================+ -| 100 arrays dict | 1.00 | 0.99 | **0.73** | 0.77 | 0.97 | 4.82 | 2.27 | 1.37 | +| 100 arrays dict | 1.00 | 0.99 | 0.63 | **0.63** | 0.88 | 3.97 | 2.24 | 1.39 | +-----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+ -| 100 dicts array | 1.00 | 1.02 | **0.82** | 0.85 | 0.90 | 5.68 | 2.19 | 1.36 | +| 100 dicts array | 1.00 | 1.04 | 0.73 | **0.70** | 0.87 | 5.79 | 2.31 | 1.36 | +-----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+ -| 256 Trues array | **1.00** | 1.04 | 1.24 | 1.09 | 1.31 | 2.41 | 2.04 | 1.12 | +| 256 Trues array | **1.00** | 1.08 | 1.23 | 1.09 | 1.35 | 3.55 | 2.31 | 1.32 | +-----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+ -| 256 ascii array | 1.00 | 1.01 | 1.04 | 1.09 | **0.49** | 1.01 | 0.95 | 1.08 | +| 256 ascii array | 1.00 | 1.01 | 1.04 | 1.03 | **0.51** | 1.17 | 1.04 | 1.12 | +-----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+ -| 256 doubles array | **1.00** | 1.02 | 1.08 | 1.03 | 6.76 | 7.34 | 6.88 | 3.89 | +| 256 doubles array | **1.00** | 1.02 | 1.06 | 1.02 | 7.45 | 8.18 | 7.69 | 4.34 | +-----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+ -| 256 unicode array | 1.00 | 0.89 | 0.89 | 0.91 | 0.56 | 0.73 | 0.83 | **0.47** | +| 256 unicode array | 1.00 | 0.88 | 0.88 | 0.88 | 0.55 | 0.74 | 0.67 | **0.51** | +-----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+ -| complex object | 1.00 | 1.01 | 0.85 | **0.82** | 1.04 | 4.11 | 2.62 | 2.16 | +| complex object | 1.00 | 1.02 | 0.81 | **0.79** | 1.13 | 3.98 | 2.84 | 2.21 | +-----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+ -| composite object | 1.00 | 1.02 | 0.72 | **0.68** | 0.88 | 2.79 | 1.82 | 1.71 | +| composite object | 1.00 | 1.00 | 0.69 | **0.65** | 0.98 | 3.12 | 1.99 | 2.00 | +-----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+ -| overall | 1.00 | 0.99 | **0.73** | 0.78 | 0.97 | 4.81 | 2.27 | 1.36 | +| overall | 1.00 | 0.99 | 0.63 | **0.63** | 0.88 | 3.97 | 2.24 | 1.39 | +-----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+ @@ -136,23 +136,23 @@ Deserialization +-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+ | deserialize | ``loads()``\ [9]_ | ``Decoder()``\ [10]_ | ``loads(n)``\ [11]_ | ``Decoder(n)``\ [12]_ | ujson | simplejson | stdlib | yajl | +=======================+=======================+=======================+=======================+=======================+=======================+=======================+=======================+=======================+ -| 100 arrays dict | 1.00 | 1.00 | 0.90 | **0.90** | 0.95 | 1.60 | 1.11 | 1.18 | +| 100 arrays dict | 1.00 | 1.00 | **0.90** | 0.90 | 0.96 | 1.40 | 1.12 | 1.15 | +-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+ -| 100 dicts array | 1.00 | 1.04 | 0.86 | **0.86** | 0.94 | 2.00 | 1.43 | 1.27 | +| 100 dicts array | 1.00 | 1.04 | **0.87** | 0.88 | 0.94 | 1.95 | 1.61 | 1.25 | +-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+ -| 256 Trues array | **1.00** | 1.16 | 1.10 | 1.12 | 1.20 | 1.95 | 1.90 | 1.89 | +| 256 Trues array | **1.00** | 1.24 | 1.17 | 1.25 | 1.16 | 2.09 | 1.87 | 1.86 | +-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+ -| 256 ascii array | **1.00** | 1.02 | 1.02 | 1.02 | 1.39 | 1.14 | 1.25 | 1.62 | +| 256 ascii array | **1.00** | 1.03 | 1.04 | 1.05 | 1.41 | 1.31 | 1.24 | 1.49 | +-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+ -| 256 doubles array | 1.00 | 0.90 | **0.16** | 0.16 | 0.39 | 0.86 | 0.83 | 0.42 | +| 256 doubles array | 1.00 | 1.01 | **0.19** | 0.19 | 0.44 | 0.91 | 0.85 | 0.56 | +-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+ -| 256 unicode array | **1.00** | 1.01 | 1.01 | 1.00 | 1.02 | 5.14 | 5.34 | 2.40 | +| 256 unicode array | **1.00** | 1.01 | 1.01 | 1.01 | 1.27 | 5.46 | 6.17 | 3.04 | +-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+ -| complex object | 1.00 | 1.01 | **0.73** | 0.73 | 0.88 | 1.60 | 1.14 | 1.20 | +| complex object | 1.00 | 1.02 | **0.78** | 0.79 | 0.99 | 1.60 | 1.19 | 1.22 | +-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+ -| composite object | 1.00 | 1.02 | **0.81** | 0.81 | 0.85 | 1.97 | 1.42 | 1.26 | +| composite object | 1.00 | 1.01 | 0.80 | 0.81 | **0.76** | 2.06 | 1.38 | 1.29 | +-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+ -| overall | 1.00 | 1.00 | 0.90 | **0.90** | 0.95 | 1.61 | 1.12 | 1.18 | +| overall | 1.00 | 1.00 | **0.90** | 0.90 | 0.96 | 1.41 | 1.13 | 1.15 | +-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+ .. [1] ``rapidjson.dumps()`` @@ -160,8 +160,8 @@ Deserialization .. [3] ``rapidjson.dumps(number_mode=NM_NATIVE)`` .. [4] ``rapidjson.Encoder(number_mode=NM_NATIVE)`` .. [5] `ujson 1.35 `__ -.. [6] `simplejson 3.11.1 `__ -.. [7] Python 3.6.2 standard library ``json`` +.. [6] `simplejson 3.13.2 `__ +.. [7] Python 3.6.4 standard library ``json`` .. [8] `yajl 0.3.5 `__ .. [9] ``rapidjson.loads()`` .. [10] ``rapidjson.Decoder()`` diff --git a/benchmarks/conftest.py b/benchmarks/conftest.py index 623e50a..08e60fa 100644 --- a/benchmarks/conftest.py +++ b/benchmarks/conftest.py @@ -23,6 +23,14 @@ def pytest_benchmark_group_stats(config, benchmarks, group_by): return sorted(result.items()) +# Remove parametrization data from JSON output to keep it to a reasonable size +# See https://github.com/ionelmc/pytest-benchmark/issues/96 + +def pytest_benchmark_update_json(config, benchmarks, output_json): + for benchmark in output_json['benchmarks']: + benchmark['params'].pop('data') + + def pytest_addoption(parser): parser.addoption('--compare-other-engines', action='store_true', help='compare against other JSON engines') diff --git a/benchmarks/tablize.py b/benchmarks/tablize.py index 62dd336..3a9bb06 100644 --- a/benchmarks/tablize.py +++ b/benchmarks/tablize.py @@ -31,7 +31,7 @@ '``rapidjson.dumps(number_mode=NM_NATIVE)``', '``rapidjson.Encoder(number_mode=NM_NATIVE)``', '`ujson 1.35 `__', - '`simplejson 3.11.1 `__', + '`simplejson 3.13.2 `__', 'Python %d.%d.%d standard library ``json``' % sys.version_info[:3], '`yajl 0.3.5 `__', '``rapidjson.loads()``', diff --git a/docs/benchmarks.rst b/docs/benchmarks.rst index 5546368..7144201 100644 --- a/docs/benchmarks.rst +++ b/docs/benchmarks.rst @@ -28,23 +28,23 @@ Serialization +-----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+ | serialize | ``dumps()``\ [1]_ | ``Encoder()``\ [2]_ | ``dumps(n)``\ [3]_ | ``Encoder(n)``\ [4]_ | ujson\ [5]_ | simplejson\ [6]_ | stdlib\ [7]_ | yajl\ [8]_ | +=======================+======================+======================+======================+======================+======================+======================+======================+======================+ -| 100 arrays dict | 1.00 | 0.99 | **0.73** | 0.77 | 0.97 | 4.82 | 2.27 | 1.37 | +| 100 arrays dict | 1.00 | 0.99 | 0.63 | **0.63** | 0.88 | 3.97 | 2.24 | 1.39 | +-----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+ -| 100 dicts array | 1.00 | 1.02 | **0.82** | 0.85 | 0.90 | 5.68 | 2.19 | 1.36 | +| 100 dicts array | 1.00 | 1.04 | 0.73 | **0.70** | 0.87 | 5.79 | 2.31 | 1.36 | +-----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+ -| 256 Trues array | **1.00** | 1.04 | 1.24 | 1.09 | 1.31 | 2.41 | 2.04 | 1.12 | +| 256 Trues array | **1.00** | 1.08 | 1.23 | 1.09 | 1.35 | 3.55 | 2.31 | 1.32 | +-----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+ -| 256 ascii array | 1.00 | 1.01 | 1.04 | 1.09 | **0.49** | 1.01 | 0.95 | 1.08 | +| 256 ascii array | 1.00 | 1.01 | 1.04 | 1.03 | **0.51** | 1.17 | 1.04 | 1.12 | +-----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+ -| 256 doubles array | **1.00** | 1.02 | 1.08 | 1.03 | 6.76 | 7.34 | 6.88 | 3.89 | +| 256 doubles array | **1.00** | 1.02 | 1.06 | 1.02 | 7.45 | 8.18 | 7.69 | 4.34 | +-----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+ -| 256 unicode array | 1.00 | 0.89 | 0.89 | 0.91 | 0.56 | 0.73 | 0.83 | **0.47** | +| 256 unicode array | 1.00 | 0.88 | 0.88 | 0.88 | 0.55 | 0.74 | 0.67 | **0.51** | +-----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+ -| complex object | 1.00 | 1.01 | 0.85 | **0.82** | 1.04 | 4.11 | 2.62 | 2.16 | +| complex object | 1.00 | 1.02 | 0.81 | **0.79** | 1.13 | 3.98 | 2.84 | 2.21 | +-----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+ -| composite object | 1.00 | 1.02 | 0.72 | **0.68** | 0.88 | 2.79 | 1.82 | 1.71 | +| composite object | 1.00 | 1.00 | 0.69 | **0.65** | 0.98 | 3.12 | 1.99 | 2.00 | +-----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+ -| overall | 1.00 | 0.99 | **0.73** | 0.78 | 0.97 | 4.81 | 2.27 | 1.36 | +| overall | 1.00 | 0.99 | 0.63 | **0.63** | 0.88 | 3.97 | 2.24 | 1.39 | +-----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+----------------------+ @@ -54,23 +54,23 @@ Deserialization +-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+ | deserialize | ``loads()``\ [9]_ | ``Decoder()``\ [10]_ | ``loads(n)``\ [11]_ | ``Decoder(n)``\ [12]_ | ujson | simplejson | stdlib | yajl | +=======================+=======================+=======================+=======================+=======================+=======================+=======================+=======================+=======================+ -| 100 arrays dict | 1.00 | 1.00 | 0.90 | **0.90** | 0.95 | 1.60 | 1.11 | 1.18 | +| 100 arrays dict | 1.00 | 1.00 | **0.90** | 0.90 | 0.96 | 1.40 | 1.12 | 1.15 | +-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+ -| 100 dicts array | 1.00 | 1.04 | 0.86 | **0.86** | 0.94 | 2.00 | 1.43 | 1.27 | +| 100 dicts array | 1.00 | 1.04 | **0.87** | 0.88 | 0.94 | 1.95 | 1.61 | 1.25 | +-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+ -| 256 Trues array | **1.00** | 1.16 | 1.10 | 1.12 | 1.20 | 1.95 | 1.90 | 1.89 | +| 256 Trues array | **1.00** | 1.24 | 1.17 | 1.25 | 1.16 | 2.09 | 1.87 | 1.86 | +-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+ -| 256 ascii array | **1.00** | 1.02 | 1.02 | 1.02 | 1.39 | 1.14 | 1.25 | 1.62 | +| 256 ascii array | **1.00** | 1.03 | 1.04 | 1.05 | 1.41 | 1.31 | 1.24 | 1.49 | +-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+ -| 256 doubles array | 1.00 | 0.90 | **0.16** | 0.16 | 0.39 | 0.86 | 0.83 | 0.42 | +| 256 doubles array | 1.00 | 1.01 | **0.19** | 0.19 | 0.44 | 0.91 | 0.85 | 0.56 | +-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+ -| 256 unicode array | **1.00** | 1.01 | 1.01 | 1.00 | 1.02 | 5.14 | 5.34 | 2.40 | +| 256 unicode array | **1.00** | 1.01 | 1.01 | 1.01 | 1.27 | 5.46 | 6.17 | 3.04 | +-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+ -| complex object | 1.00 | 1.01 | **0.73** | 0.73 | 0.88 | 1.60 | 1.14 | 1.20 | +| complex object | 1.00 | 1.02 | **0.78** | 0.79 | 0.99 | 1.60 | 1.19 | 1.22 | +-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+ -| composite object | 1.00 | 1.02 | **0.81** | 0.81 | 0.85 | 1.97 | 1.42 | 1.26 | +| composite object | 1.00 | 1.01 | 0.80 | 0.81 | **0.76** | 2.06 | 1.38 | 1.29 | +-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+ -| overall | 1.00 | 1.00 | 0.90 | **0.90** | 0.95 | 1.61 | 1.12 | 1.18 | +| overall | 1.00 | 1.00 | **0.90** | 0.90 | 0.96 | 1.41 | 1.13 | 1.15 | +-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+-----------------------+ .. [1] ``rapidjson.dumps()`` @@ -78,8 +78,8 @@ Deserialization .. [3] ``rapidjson.dumps(number_mode=NM_NATIVE)`` .. [4] ``rapidjson.Encoder(number_mode=NM_NATIVE)`` .. [5] `ujson 1.35 `__ -.. [6] `simplejson 3.11.1 `__ -.. [7] Python 3.6.2 standard library ``json`` +.. [6] `simplejson 3.13.2 `__ +.. [7] Python 3.6.4 standard library ``json`` .. [8] `yajl 0.3.5 `__ .. [9] ``rapidjson.loads()`` .. [10] ``rapidjson.Decoder()`` diff --git a/docs/quickstart.rst b/docs/quickstart.rst index b892214..ce10da8 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -72,6 +72,19 @@ All JSON_ data types are supported using their native Python counterparts: 'string': '√2 ≅ 1.4142', 'true': True}} +Python's lists, tuples and iterators get serialized as JSON arrays: + +.. doctest:: + + >>> names_t = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat') + >>> names_l = list(names_t) + >>> names_i = iter(names_l) + >>> def names_g(): + ... for name in names_t: + ... yield name + >>> dumps(names_t) == dumps(names_l) == dumps(names_i) == dumps(names_g()) + True + ``python-rapidjson`` can optionally handle also a few other commonly used data types: .. doctest:: diff --git a/rapidjson.cpp b/rapidjson.cpp index ad52884..b058fcf 100644 --- a/rapidjson.cpp +++ b/rapidjson.cpp @@ -1506,30 +1506,6 @@ decoder_new(PyTypeObject* type, PyObject* args, PyObject* kwargs) ///////////// -struct WriterContext { - const char* key; - Py_ssize_t size; - PyObject* object; - PyObject* decref; - unsigned level; - bool isObject; - - WriterContext(const char* k, - Py_ssize_t s, - PyObject* o, - bool isO, - int l, - PyObject* d=NULL) - : key(k), - size(s), - object(o), - decref(d), - level(l), - isObject(isO) - {} -}; - - struct DictItem { const char* key_str; Py_ssize_t key_size; @@ -1546,8 +1522,8 @@ struct DictItem { bool operator<(const DictItem& other) const { Py_ssize_t tks = this->key_size; Py_ssize_t oks = other.key_size; - int cmp = strncmp(other.key_str, this->key_str, oks < tks ? oks : tks); - return (cmp == 0) ? oks < tks : cmp < 0; + int cmp = strncmp(this->key_str, other.key_str, tks < oks ? tks : oks); + return (cmp == 0) ? (tks < oks) : (cmp < 0); } }; @@ -1555,12 +1531,11 @@ struct DictItem { static const int MAX_RECURSION_DEPTH = 2048; -template -static PyObject* +template +static bool dumps_internal( WriterT* writer, - BufferT* buf, - PyObject* value, + PyObject* object, bool skipKeys, PyObject* defaultFn, bool sortKeys, @@ -1569,472 +1544,389 @@ dumps_internal( DatetimeMode datetimeMode, UuidMode uuidMode) { - int isDec; - std::vector stack; - stack.reserve(128); + int is_decimal; - stack.push_back(WriterContext(NULL, 0, value, false, 0)); + if (maxRecursionDepth == 0) { + PyErr_SetString(PyExc_OverflowError, "Max recursion depth reached"); + return false; + } - while (!stack.empty()) { - const WriterContext& current = stack.back(); - stack.pop_back(); +#define RECURSE(v) (dumps_internal(writer, v, skipKeys, defaultFn, sortKeys, \ + maxRecursionDepth-1, \ + numberMode, datetimeMode, uuidMode)) - const unsigned currentLevel = current.level; - const unsigned nextLevel = current.level + 1; - PyObject* const object = current.object; + if (object == Py_None) { + writer->Null(); + } + else if (PyBool_Check(object)) { + writer->Bool(object == Py_True); + } + else if (numberMode & NM_DECIMAL + && (is_decimal = PyObject_IsInstance(object, decimal_type))) { + if (is_decimal == -1) + return false; - if (currentLevel > maxRecursionDepth) { - PyErr_SetString(PyExc_OverflowError, "Max recursion depth reached"); - return NULL; - } + if (!(numberMode & NM_NAN)) { + bool is_inf_or_nan; + PyObject* is_inf = PyObject_CallMethodObjArgs(object, is_infinite_name, + NULL); - if (object == NULL) { - if (current.key != NULL) { - writer->Key(current.key, current.size); - } - else if (current.decref != NULL) { - Py_DECREF(current.decref); - } - else if (current.isObject) { - writer->EndObject(); - } - else { - writer->EndArray(); + if (is_inf == NULL) { + return false; } - } - else if (object == Py_None) { - writer->Null(); - } - else if (PyBool_Check(object)) { - writer->Bool(object == Py_True); - } - else if (numberMode & NM_DECIMAL - && (isDec = PyObject_IsInstance(object, decimal_type))) { - if (isDec == -1) - return NULL; + is_inf_or_nan = is_inf == Py_True; + Py_DECREF(is_inf); - if (!(numberMode & NM_NAN)) { - bool is_inf_or_nan; - PyObject* is_inf = PyObject_CallMethodObjArgs(object, is_infinite_name, + if (!is_inf_or_nan) { + PyObject* is_nan = PyObject_CallMethodObjArgs(object, is_nan_name, NULL); - if (is_inf == NULL) { - return NULL; - } - is_inf_or_nan = is_inf == Py_True; - Py_DECREF(is_inf); - - if (!is_inf_or_nan) { - PyObject* is_nan = PyObject_CallMethodObjArgs(object, is_nan_name, - NULL); - - if (is_nan == NULL) { - return NULL; - } - is_inf_or_nan = is_nan == Py_True; - Py_DECREF(is_nan); - } - - if (is_inf_or_nan) { - PyErr_SetString(PyExc_ValueError, - "Out of range decimal values are not JSON compliant"); - return NULL; + if (is_nan == NULL) { + return false; } + is_inf_or_nan = is_nan == Py_True; + Py_DECREF(is_nan); } - PyObject* decStrObj = PyObject_Str(object); - if (decStrObj == NULL) - return NULL; - - Py_ssize_t size; - const char* decStr = PyUnicode_AsUTF8AndSize(decStrObj, &size); - if (decStr == NULL) { - Py_DECREF(decStrObj); - return NULL; + if (is_inf_or_nan) { + PyErr_SetString(PyExc_ValueError, + "Out of range decimal values are not JSON compliant"); + return false; } + } + + PyObject* decStrObj = PyObject_Str(object); + if (decStrObj == NULL) + return false; - writer->RawValue(decStr, size, kNumberType); + Py_ssize_t size; + const char* decStr = PyUnicode_AsUTF8AndSize(decStrObj, &size); + if (decStr == NULL) { Py_DECREF(decStrObj); + return false; } - else if (PyLong_Check(object)) { - if (numberMode & NM_NATIVE) { - int overflow; - long long i = PyLong_AsLongLongAndOverflow(object, &overflow); - if (i == -1 && PyErr_Occurred()) - return NULL; - if (overflow == 0) { - writer->Int64(i); - } else { - unsigned long long ui = PyLong_AsUnsignedLongLong(object); - if (PyErr_Occurred()) - return NULL; + writer->RawValue(decStr, size, kNumberType); + Py_DECREF(decStrObj); + } + else if (PyLong_Check(object)) { + if (numberMode & NM_NATIVE) { + int overflow; + long long i = PyLong_AsLongLongAndOverflow(object, &overflow); + if (i == -1 && PyErr_Occurred()) + return false; - writer->Uint64(ui); - } + if (overflow == 0) { + writer->Int64(i); } else { - PyObject* intStrObj = PyObject_Str(object); - if (intStrObj == NULL) - return NULL; - - Py_ssize_t size; - const char* intStr = PyUnicode_AsUTF8AndSize(intStrObj, &size); - if (intStr == NULL) { - Py_DECREF(intStrObj); - return NULL; - } + unsigned long long ui = PyLong_AsUnsignedLongLong(object); + if (PyErr_Occurred()) + return false; - writer->RawValue(intStr, size, kNumberType); + writer->Uint64(ui); + } + } else { + PyObject* intStrObj = PyObject_Str(object); + if (intStrObj == NULL) + return false; + + Py_ssize_t size; + const char* intStr = PyUnicode_AsUTF8AndSize(intStrObj, &size); + if (intStr == NULL) { Py_DECREF(intStrObj); + return false; } + + writer->RawValue(intStr, size, kNumberType); + Py_DECREF(intStrObj); } - else if (PyFloat_Check(object)) { - double d = PyFloat_AsDouble(object); - if (d == -1.0 && PyErr_Occurred()) - return NULL; + } + else if (PyFloat_Check(object)) { + double d = PyFloat_AsDouble(object); + if (d == -1.0 && PyErr_Occurred()) + return false; - if (IS_NAN(d)) { - if (numberMode & NM_NAN) - writer->RawValue("NaN", 3, kNumberType); - else { - PyErr_SetString(PyExc_ValueError, - "Out of range float values are not JSON compliant"); - return NULL; - } - } else if (IS_INF(d)) { - if (!(numberMode & NM_NAN)) { - PyErr_SetString(PyExc_ValueError, - "Out of range float values are not JSON compliant"); - return NULL; - } - else if (d < 0) - writer->RawValue("-Infinity", 9, kNumberType); - else - writer->RawValue("Infinity", 8, kNumberType); + if (IS_NAN(d)) { + if (numberMode & NM_NAN) + writer->RawValue("NaN", 3, kNumberType); + else { + PyErr_SetString(PyExc_ValueError, + "Out of range float values are not JSON compliant"); + return false; } + } else if (IS_INF(d)) { + if (!(numberMode & NM_NAN)) { + PyErr_SetString(PyExc_ValueError, + "Out of range float values are not JSON compliant"); + return false; + } + else if (d < 0) + writer->RawValue("-Infinity", 9, kNumberType); else - writer->Double(d); + writer->RawValue("Infinity", 8, kNumberType); } - else if (PyBytes_Check(object)) { - Py_ssize_t l; - char* s; + else + writer->Double(d); + } + else if (PyBytes_Check(object)) { + Py_ssize_t l; + char* s; - if (PyBytes_AsStringAndSize(object, &s, &l) == -1) - goto error; + if (PyBytes_AsStringAndSize(object, &s, &l) == -1) + return false; - writer->String(s, l); - } - else if (PyUnicode_Check(object)) { - Py_ssize_t l; - const char* s = PyUnicode_AsUTF8AndSize(object, &l); - if (s == NULL) - return NULL; - writer->String(s, l); + writer->String(s, l); + } + else if (PyUnicode_Check(object)) { + Py_ssize_t l; + const char* s = PyUnicode_AsUTF8AndSize(object, &l); + if (s == NULL) + return false; + writer->String(s, l); + } + else if (PyList_Check(object)) { + writer->StartArray(); + + Py_ssize_t size = PyList_GET_SIZE(object); + + for (Py_ssize_t i = 0; i < size; i++) { + PyObject* item = PyList_GET_ITEM(object, i); + if (!RECURSE(item)) + return false; } - else if (PyList_Check(object)) { - writer->StartArray(); - stack.push_back(WriterContext(NULL, 0, NULL, false, currentLevel)); - Py_ssize_t size = PyList_GET_SIZE(object); + writer->EndArray(); + } + else if (PyTuple_Check(object)) { + writer->StartArray(); - for (Py_ssize_t i = size - 1; i >= 0; --i) { - PyObject* item = PyList_GET_ITEM(object, i); - stack.push_back(WriterContext(NULL, 0, item, false, nextLevel)); - } + Py_ssize_t size = PyTuple_GET_SIZE(object); + + for (Py_ssize_t i = 0; i < size; i++) { + PyObject* item = PyTuple_GET_ITEM(object, i); + if (!RECURSE(item)) + return false; } - else if (PyTuple_Check(object)) { - writer->StartArray(); - stack.push_back(WriterContext(NULL, 0, NULL, false, currentLevel)); - Py_ssize_t size = PyTuple_GET_SIZE(object); + writer->EndArray(); + } + else if (PyDict_Check(object)) { + writer->StartObject(); + + Py_ssize_t pos = 0; + PyObject* key; + PyObject* item; - for (Py_ssize_t i = size - 1; i >= 0; --i) { - PyObject* item = PyTuple_GET_ITEM(object, i); - stack.push_back(WriterContext(NULL, 0, item, false, nextLevel)); + if (!sortKeys) { + while (PyDict_Next(object, &pos, &key, &item)) { + if (PyUnicode_Check(key)) { + Py_ssize_t l; + const char* key_str = PyUnicode_AsUTF8AndSize(key, &l); + if (key_str == NULL) + return false; + writer->Key(key_str, l); + if (!RECURSE(item)) + return false; + } + else if (!skipKeys) { + PyErr_SetString(PyExc_TypeError, "keys must be strings"); + return false; + } } } - else if (PyDict_Check(object)) { - writer->StartObject(); - stack.push_back(WriterContext(NULL, 0, NULL, true, currentLevel)); - - Py_ssize_t pos = 0; - PyObject* key; - PyObject* item; - - if (!sortKeys) { - while (PyDict_Next(object, &pos, &key, &item)) { - if (PyUnicode_Check(key)) { - Py_ssize_t l; - const char* key_str = PyUnicode_AsUTF8AndSize(key, &l); - if (key_str == NULL) - goto error; - stack.push_back(WriterContext(NULL, 0, item, false, nextLevel)); - stack.push_back(WriterContext(key_str, l, NULL, false, nextLevel)); - } - else if (!skipKeys) { - PyErr_SetString(PyExc_TypeError, "keys must be strings"); - goto error; - } + else { + std::vector items; + + while (PyDict_Next(object, &pos, &key, &item)) { + if (PyUnicode_Check(key)) { + Py_ssize_t l; + const char* key_str = PyUnicode_AsUTF8AndSize(key, &l); + if (key_str == NULL) + return false; + items.push_back(DictItem(key_str, l, item)); } - } - else { - std::vector items; - - while (PyDict_Next(object, &pos, &key, &item)) { - if (PyUnicode_Check(key)) { - Py_ssize_t l; - const char* key_str = PyUnicode_AsUTF8AndSize(key, &l); - if (key_str == NULL) - goto error; - items.push_back(DictItem(key_str, l, item)); - } - else if (!skipKeys) { - PyErr_SetString(PyExc_TypeError, "keys must be strings"); - goto error; - } + else if (!skipKeys) { + PyErr_SetString(PyExc_TypeError, "keys must be strings"); + return false; } + } - std::sort(items.begin(), items.end()); + std::sort(items.begin(), items.end()); - std::vector::const_iterator iter = items.begin(); - for (; iter != items.end(); ++iter) { - stack.push_back(WriterContext(NULL, 0, iter->item, false, nextLevel)); - stack.push_back(WriterContext(iter->key_str, iter->key_size, - NULL, false, nextLevel)); - } + for (int i=0, s=items.size(); i < s; i++) { + writer->Key(items[i].key_str, items[i].key_size); + if (!RECURSE(items[i].item)) + return false; } } - else if (datetimeMode != DM_NONE - && (PyTime_Check(object) || PyDateTime_Check(object))) { - unsigned int year, month, day, hour, min, sec, microsec; - PyObject* dtObject = object; - PyObject* asUTC = NULL; - const int ISOFORMAT_LEN = 40; - char isoformat[ISOFORMAT_LEN]; - memset(isoformat, 0, ISOFORMAT_LEN); + writer->EndObject(); + } + else if (datetimeMode != DM_NONE + && (PyTime_Check(object) || PyDateTime_Check(object))) { + unsigned int year, month, day, hour, min, sec, microsec; + PyObject* dtObject = object; + PyObject* asUTC = NULL; - const int TIMEZONE_LEN = 16; - char timeZone[TIMEZONE_LEN] = { 0 }; + const int ISOFORMAT_LEN = 40; + char isoformat[ISOFORMAT_LEN]; + memset(isoformat, 0, ISOFORMAT_LEN); - if (!(datetimeMode & DM_IGNORE_TZ) - && PyObject_HasAttr(object, utcoffset_name)) { - PyObject* utcOffset = PyObject_CallMethodObjArgs(object, - utcoffset_name, - NULL); + const int TIMEZONE_LEN = 16; + char timeZone[TIMEZONE_LEN] = { 0 }; - if (!utcOffset) - goto error; - - if (utcOffset == Py_None) { - // Naive value: maybe assume it's in UTC instead of local time - if (datetimeMode & DM_NAIVE_IS_UTC) { - if (PyDateTime_Check(object)) { - hour = PyDateTime_DATE_GET_HOUR(dtObject); - min = PyDateTime_DATE_GET_MINUTE(dtObject); - sec = PyDateTime_DATE_GET_SECOND(dtObject); - microsec = PyDateTime_DATE_GET_MICROSECOND(dtObject); - year = PyDateTime_GET_YEAR(dtObject); - month = PyDateTime_GET_MONTH(dtObject); - day = PyDateTime_GET_DAY(dtObject); - - asUTC = PyDateTimeAPI->DateTime_FromDateAndTime( - year, month, day, hour, min, sec, microsec, - timezone_utc, PyDateTimeAPI->DateTimeType); - } else { - hour = PyDateTime_TIME_GET_HOUR(dtObject); - min = PyDateTime_TIME_GET_MINUTE(dtObject); - sec = PyDateTime_TIME_GET_SECOND(dtObject); - microsec = PyDateTime_TIME_GET_MICROSECOND(dtObject); - asUTC = PyDateTimeAPI->Time_FromTime( - hour, min, sec, microsec, - timezone_utc, PyDateTimeAPI->TimeType); - } - - if (asUTC == NULL) { - Py_DECREF(utcOffset); - goto error; - } + if (!(datetimeMode & DM_IGNORE_TZ) + && PyObject_HasAttr(object, utcoffset_name)) { + PyObject* utcOffset = PyObject_CallMethodObjArgs(object, + utcoffset_name, + NULL); - dtObject = asUTC; + if (!utcOffset) + return false; - if (datetime_mode_format(datetimeMode) == DM_ISO8601) - strcpy(timeZone, "+00:00"); + if (utcOffset == Py_None) { + // Naive value: maybe assume it's in UTC instead of local time + if (datetimeMode & DM_NAIVE_IS_UTC) { + if (PyDateTime_Check(object)) { + hour = PyDateTime_DATE_GET_HOUR(dtObject); + min = PyDateTime_DATE_GET_MINUTE(dtObject); + sec = PyDateTime_DATE_GET_SECOND(dtObject); + microsec = PyDateTime_DATE_GET_MICROSECOND(dtObject); + year = PyDateTime_GET_YEAR(dtObject); + month = PyDateTime_GET_MONTH(dtObject); + day = PyDateTime_GET_DAY(dtObject); + + asUTC = PyDateTimeAPI->DateTime_FromDateAndTime( + year, month, day, hour, min, sec, microsec, + timezone_utc, PyDateTimeAPI->DateTimeType); + } else { + hour = PyDateTime_TIME_GET_HOUR(dtObject); + min = PyDateTime_TIME_GET_MINUTE(dtObject); + sec = PyDateTime_TIME_GET_SECOND(dtObject); + microsec = PyDateTime_TIME_GET_MICROSECOND(dtObject); + asUTC = PyDateTimeAPI->Time_FromTime( + hour, min, sec, microsec, + timezone_utc, PyDateTimeAPI->TimeType); } - } else { - // Timezone-aware value - if (datetimeMode & DM_SHIFT_TO_UTC) { - // If it's not already in UTC, shift the value - if (PyObject_IsTrue(utcOffset)) { - asUTC = PyObject_CallMethodObjArgs(object, astimezone_name, - timezone_utc, NULL); - - if (asUTC == NULL) { - Py_DECREF(utcOffset); - goto error; - } - dtObject = asUTC; - } + if (asUTC == NULL) { + Py_DECREF(utcOffset); + return false; + } - if (datetime_mode_format(datetimeMode) == DM_ISO8601) - strcpy(timeZone, "+00:00"); - } else if (datetime_mode_format(datetimeMode) == DM_ISO8601) { - int seconds_from_utc = 0; + dtObject = asUTC; - if (PyObject_IsTrue(utcOffset)) { - PyObject* tsObj = PyObject_CallMethodObjArgs(utcOffset, - total_seconds_name, - NULL); + if (datetime_mode_format(datetimeMode) == DM_ISO8601) + strcpy(timeZone, "+00:00"); + } + } else { + // Timezone-aware value + if (datetimeMode & DM_SHIFT_TO_UTC) { + // If it's not already in UTC, shift the value + if (PyObject_IsTrue(utcOffset)) { + asUTC = PyObject_CallMethodObjArgs(object, astimezone_name, + timezone_utc, NULL); - if (tsObj == NULL) { - Py_DECREF(utcOffset); - goto error; - } + if (asUTC == NULL) { + Py_DECREF(utcOffset); + return false; + } - seconds_from_utc = (int) PyFloat_AsDouble(tsObj); + dtObject = asUTC; + } - Py_DECREF(tsObj); - } + if (datetime_mode_format(datetimeMode) == DM_ISO8601) + strcpy(timeZone, "+00:00"); + } else if (datetime_mode_format(datetimeMode) == DM_ISO8601) { + int seconds_from_utc = 0; - char sign = '+'; + if (PyObject_IsTrue(utcOffset)) { + PyObject* tsObj = PyObject_CallMethodObjArgs(utcOffset, + total_seconds_name, + NULL); - if (seconds_from_utc < 0) { - sign = '-'; - seconds_from_utc = -seconds_from_utc; + if (tsObj == NULL) { + Py_DECREF(utcOffset); + return false; } - unsigned int tz_hour = seconds_from_utc / 3600; - unsigned int tz_min = (seconds_from_utc % 3600) / 60; + seconds_from_utc = (int) PyFloat_AsDouble(tsObj); - snprintf(timeZone, TIMEZONE_LEN-1, "%c%02u:%02u", - sign, tz_hour, tz_min); + Py_DECREF(tsObj); } - } - Py_DECREF(utcOffset); - } - if (datetime_mode_format(datetimeMode) == DM_ISO8601) { - if (PyDateTime_Check(dtObject)) { - year = PyDateTime_GET_YEAR(dtObject); - month = PyDateTime_GET_MONTH(dtObject); - day = PyDateTime_GET_DAY(dtObject); - hour = PyDateTime_DATE_GET_HOUR(dtObject); - min = PyDateTime_DATE_GET_MINUTE(dtObject); - sec = PyDateTime_DATE_GET_SECOND(dtObject); - microsec = PyDateTime_DATE_GET_MICROSECOND(dtObject); - - if (microsec > 0) { - snprintf(isoformat, - ISOFORMAT_LEN-1, - "%04u-%02u-%02uT%02u:%02u:%02u.%06u%s", - year, month, day, - hour, min, sec, microsec, - timeZone); - } else { - snprintf(isoformat, - ISOFORMAT_LEN-1, - "%04u-%02u-%02uT%02u:%02u:%02u%s", - year, month, day, - hour, min, sec, - timeZone); - } - } else { - hour = PyDateTime_TIME_GET_HOUR(dtObject); - min = PyDateTime_TIME_GET_MINUTE(dtObject); - sec = PyDateTime_TIME_GET_SECOND(dtObject); - microsec = PyDateTime_TIME_GET_MICROSECOND(dtObject); - - if (microsec > 0) { - snprintf(isoformat, - ISOFORMAT_LEN-1, - "%02u:%02u:%02u.%06u%s", - hour, min, sec, microsec, - timeZone); - } else { - snprintf(isoformat, - ISOFORMAT_LEN-1, - "%02u:%02u:%02u%s", - hour, min, sec, - timeZone); - } - } - writer->String(isoformat); - } else /* if (datetimeMode & DM_UNIX_TIME) */ { - if (PyDateTime_Check(dtObject)) { - PyObject* timestampObj = PyObject_CallMethodObjArgs(dtObject, - timestamp_name, - NULL); - - if (!timestampObj) { - Py_XDECREF(asUTC); - goto error; - } + char sign = '+'; - double timestamp = PyFloat_AsDouble(timestampObj); - - Py_DECREF(timestampObj); - - if (datetimeMode & DM_ONLY_SECONDS) - writer->Int64((int64_t) timestamp); - else { - int precision = writer->GetMaxDecimalPlaces(); - writer->SetMaxDecimalPlaces(6); - writer->Double(timestamp); - writer->SetMaxDecimalPlaces(precision); + if (seconds_from_utc < 0) { + sign = '-'; + seconds_from_utc = -seconds_from_utc; } - } else { - hour = PyDateTime_TIME_GET_HOUR(dtObject); - min = PyDateTime_TIME_GET_MINUTE(dtObject); - sec = PyDateTime_TIME_GET_SECOND(dtObject); - microsec = PyDateTime_TIME_GET_MICROSECOND(dtObject); - long timestamp = hour * 3600 + min * 60 + sec; + unsigned int tz_hour = seconds_from_utc / 3600; + unsigned int tz_min = (seconds_from_utc % 3600) / 60; - if (datetimeMode & DM_ONLY_SECONDS) - writer->Int64(timestamp); - else - writer->Double(timestamp + (microsec / 1000000.0)); + snprintf(timeZone, TIMEZONE_LEN-1, "%c%02u:%02u", + sign, tz_hour, tz_min); } } - Py_XDECREF(asUTC); - } - else if (datetimeMode != DM_NONE && PyDate_Check(object)) { - unsigned int year = PyDateTime_GET_YEAR(object); - unsigned int month = PyDateTime_GET_MONTH(object); - unsigned int day = PyDateTime_GET_DAY(object); - - if (datetime_mode_format(datetimeMode) == DM_ISO8601) { - const int ISOFORMAT_LEN = 16; - char isoformat[ISOFORMAT_LEN]; - memset(isoformat, 0, ISOFORMAT_LEN); - - snprintf(isoformat, ISOFORMAT_LEN-1, "%04u-%02u-%02u", year, month, day); - writer->String(isoformat); - } else /* datetime_mode_format(datetimeMode) == DM_UNIX_TIME */ { - // A date object, take its midnight timestamp - PyObject* midnightObj; - PyObject* timestampObj; - - if (datetimeMode & (DM_SHIFT_TO_UTC | DM_NAIVE_IS_UTC)) - midnightObj = PyDateTimeAPI->DateTime_FromDateAndTime( - year, month, day, 0, 0, 0, 0, - timezone_utc, PyDateTimeAPI->DateTimeType); - else - midnightObj = PyDateTime_FromDateAndTime(year, month, day, - 0, 0, 0, 0); - - if (!midnightObj) { - goto error; + Py_DECREF(utcOffset); + } + + if (datetime_mode_format(datetimeMode) == DM_ISO8601) { + if (PyDateTime_Check(dtObject)) { + year = PyDateTime_GET_YEAR(dtObject); + month = PyDateTime_GET_MONTH(dtObject); + day = PyDateTime_GET_DAY(dtObject); + hour = PyDateTime_DATE_GET_HOUR(dtObject); + min = PyDateTime_DATE_GET_MINUTE(dtObject); + sec = PyDateTime_DATE_GET_SECOND(dtObject); + microsec = PyDateTime_DATE_GET_MICROSECOND(dtObject); + + if (microsec > 0) { + snprintf(isoformat, + ISOFORMAT_LEN-1, + "%04u-%02u-%02uT%02u:%02u:%02u.%06u%s", + year, month, day, + hour, min, sec, microsec, + timeZone); + } else { + snprintf(isoformat, + ISOFORMAT_LEN-1, + "%04u-%02u-%02uT%02u:%02u:%02u%s", + year, month, day, + hour, min, sec, + timeZone); } - - timestampObj = PyObject_CallMethodObjArgs(midnightObj, timestamp_name, - NULL); - - Py_DECREF(midnightObj); + } else { + hour = PyDateTime_TIME_GET_HOUR(dtObject); + min = PyDateTime_TIME_GET_MINUTE(dtObject); + sec = PyDateTime_TIME_GET_SECOND(dtObject); + microsec = PyDateTime_TIME_GET_MICROSECOND(dtObject); + + if (microsec > 0) { + snprintf(isoformat, + ISOFORMAT_LEN-1, + "%02u:%02u:%02u.%06u%s", + hour, min, sec, microsec, + timeZone); + } else { + snprintf(isoformat, + ISOFORMAT_LEN-1, + "%02u:%02u:%02u%s", + hour, min, sec, + timeZone); + } + } + writer->String(isoformat); + } else /* if (datetimeMode & DM_UNIX_TIME) */ { + if (PyDateTime_Check(dtObject)) { + PyObject* timestampObj = PyObject_CallMethodObjArgs(dtObject, + timestamp_name, + NULL); if (!timestampObj) { - goto error; + Py_XDECREF(asUTC); + return false; } double timestamp = PyFloat_AsDouble(timestampObj); @@ -2049,59 +1941,149 @@ dumps_internal( writer->Double(timestamp); writer->SetMaxDecimalPlaces(precision); } + } else { + hour = PyDateTime_TIME_GET_HOUR(dtObject); + min = PyDateTime_TIME_GET_MINUTE(dtObject); + sec = PyDateTime_TIME_GET_SECOND(dtObject); + microsec = PyDateTime_TIME_GET_MICROSECOND(dtObject); + + long timestamp = hour * 3600 + min * 60 + sec; + + if (datetimeMode & DM_ONLY_SECONDS) + writer->Int64(timestamp); + else + writer->Double(timestamp + (microsec / 1000000.0)); } } - else if (uuidMode != UM_NONE - && PyObject_TypeCheck(object, (PyTypeObject*) uuid_type)) { - PyObject* retval; - if (uuidMode == UM_CANONICAL) - retval = PyObject_Str(object); + Py_XDECREF(asUTC); + } + else if (datetimeMode != DM_NONE && PyDate_Check(object)) { + unsigned int year = PyDateTime_GET_YEAR(object); + unsigned int month = PyDateTime_GET_MONTH(object); + unsigned int day = PyDateTime_GET_DAY(object); + + if (datetime_mode_format(datetimeMode) == DM_ISO8601) { + const int ISOFORMAT_LEN = 16; + char isoformat[ISOFORMAT_LEN]; + memset(isoformat, 0, ISOFORMAT_LEN); + + snprintf(isoformat, ISOFORMAT_LEN-1, "%04u-%02u-%02u", year, month, day); + writer->String(isoformat); + } else /* datetime_mode_format(datetimeMode) == DM_UNIX_TIME */ { + // A date object, take its midnight timestamp + PyObject* midnightObj; + PyObject* timestampObj; + + if (datetimeMode & (DM_SHIFT_TO_UTC | DM_NAIVE_IS_UTC)) + midnightObj = PyDateTimeAPI->DateTime_FromDateAndTime( + year, month, day, 0, 0, 0, 0, + timezone_utc, PyDateTimeAPI->DateTimeType); else - retval = PyObject_GetAttr(object, hex_name); - if (retval == NULL) - goto error; + midnightObj = PyDateTime_FromDateAndTime(year, month, day, + 0, 0, 0, 0); + + if (!midnightObj) { + return false; + } + + timestampObj = PyObject_CallMethodObjArgs(midnightObj, timestamp_name, + NULL); + + Py_DECREF(midnightObj); + + if (!timestampObj) { + return false; + } + + double timestamp = PyFloat_AsDouble(timestampObj); + + Py_DECREF(timestampObj); - // Decref the return value once it's done being dumped to a string. - stack.push_back(WriterContext(NULL, 0, NULL, false, currentLevel, retval)); - stack.push_back(WriterContext(NULL, 0, retval, false, currentLevel)); + if (datetimeMode & DM_ONLY_SECONDS) + writer->Int64((int64_t) timestamp); + else { + int precision = writer->GetMaxDecimalPlaces(); + writer->SetMaxDecimalPlaces(6); + writer->Double(timestamp); + writer->SetMaxDecimalPlaces(precision); + } + } + } + else if (uuidMode != UM_NONE + && PyObject_TypeCheck(object, (PyTypeObject*) uuid_type)) { + PyObject* retval; + if (uuidMode == UM_CANONICAL) + retval = PyObject_Str(object); + else + retval = PyObject_GetAttr(object, hex_name); + if (retval == NULL) + return false; + if (!RECURSE(retval)) { + Py_DECREF(retval); + return false; } - else if (defaultFn) { - PyObject* retval = PyObject_CallFunctionObjArgs(defaultFn, object, NULL); - if (retval == NULL) - goto error; + Py_DECREF(retval); + } + else if (PyIter_Check(object)) { + PyObject* iterator = PyObject_GetIter(object); + if (iterator == NULL) + return false; + + writer->StartArray(); - // Decref the return value once it's done being dumped to a string. - stack.push_back(WriterContext(NULL, 0, NULL, false, currentLevel, retval)); - stack.push_back(WriterContext(NULL, 0, retval, false, currentLevel)); + PyObject* item; + while ((item = PyIter_Next(iterator))) { + if (!RECURSE(item)) { + Py_DECREF(item); + Py_DECREF(iterator); + return false; + } + Py_DECREF(item); } - else { - PyObject* repr = PyObject_Repr(object); - PyErr_Format(PyExc_TypeError, "%s is not JSON serializable", - PyUnicode_AsUTF8(repr)); - Py_XDECREF(repr); - goto error; + + Py_DECREF(iterator); + + // PyIter_Next() may exit with an error + if (PyErr_Occurred()) + return false; + + writer->EndArray(); + } + else if (defaultFn) { + PyObject* retval = PyObject_CallFunctionObjArgs(defaultFn, object, NULL); + if (retval == NULL) + return false; + if (!RECURSE(retval)) { + Py_DECREF(retval); + return false; } + Py_DECREF(retval); + } + else { + PyObject* repr = PyObject_Repr(object); + PyErr_Format(PyExc_TypeError, "%s is not JSON serializable", + PyUnicode_AsUTF8(repr)); + Py_XDECREF(repr); + return false; } - return PyUnicode_FromString(buf->GetString()); + return true; -error: - return NULL; +#undef RECURSE } -#define DUMPS_INTERNAL_CALL \ - dumps_internal( \ - &writer, \ - &buf, \ - value, \ - skipInvalidKeys, \ - defaultFn, \ - sortKeys, \ - maxRecursionDepth, \ - numberMode, \ - datetimeMode, \ - uuidMode) +#define DUMPS_INTERNAL_CALL \ + (dumps_internal(&writer, \ + value, \ + skipInvalidKeys, \ + defaultFn, \ + sortKeys, \ + maxRecursionDepth, \ + numberMode, \ + datetimeMode, \ + uuidMode) \ + ? PyUnicode_FromString(buf.GetString()) : NULL) typedef struct { diff --git a/requirements.txt b/requirements.txt index 0d5e887..8fa461c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ pytest-benchmark==3.1.1 # Keep in sync with benchmarks/tablize.py versions -simplejson==3.11.1 +simplejson==3.13.2 ujson==1.35 yajl==0.3.5 diff --git a/tests/test_base_types.py b/tests/test_base_types.py index 1fca144..b952d07 100644 --- a/tests/test_base_types.py +++ b/tests/test_base_types.py @@ -28,6 +28,12 @@ def test_base_values(value, dumps, loads): assert loaded == value and type(loaded) is type(value) +@pytest.mark.unit +def test_tuple(dumps): + obj = [1, 2, 'a', 1.2, {'foo': 'bar'},] + assert dumps(obj) == dumps(tuple(obj)) + + @pytest.mark.unit def test_bytes_value(dumps): dumped = dumps(b'cruel\x00world') @@ -163,6 +169,18 @@ def test_constants(dumps, loads): assert loads(dumps({'a': c}))['a'] is c +@pytest.mark.unit +def test_iterables(dumps): + assert dumps(iter("abc")) == '["a","b","c"]' + + def gen(): + yield 1 + yield 2 + yield 3 + + assert dumps(gen()) == '[1,2,3]' + + # TODO: Figure out what we want to do here bad_tests = """ @pytest.mark.unit diff --git a/tests/test_refs_count.py b/tests/test_refs_count.py index 2db252d..9be52a5 100644 --- a/tests/test_refs_count.py +++ b/tests/test_refs_count.py @@ -91,3 +91,14 @@ def test_leaks(value, dumps_options, loads_options): del aspython rc1 = sys.gettotalrefcount() assert (rc1 - rc0) < THRESHOLD + + +@pytest.mark.skipif(not hasattr(sys, 'gettotalrefcount'), reason='Non-debug Python') +def test_dump_iterator_leaks(): + rc0 = sys.gettotalrefcount() + value = iter(range(10000)) + asjson = rj.dumps(value) + del asjson + del value + rc1 = sys.gettotalrefcount() + assert (rc1 - rc0) < THRESHOLD