summaryrefslogtreecommitdiff
path: root/simplejson
diff options
context:
space:
mode:
authorBob Ippolito <bob@redivi.com>2013-05-01 13:00:06 -0700
committerBob Ippolito <bob@redivi.com>2013-05-01 13:00:06 -0700
commit236c8a174c0e333608e992f6f7ec7826c2fd1cd8 (patch)
tree3a75f4ab4e8a7d6a1af1dd793a0cea99bf0a6fe0 /simplejson
parent928421f68ff2128c5397553a57f3d76a75e39ca4 (diff)
downloadsimplejson-236c8a174c0e333608e992f6f7ec7826c2fd1cd8.tar.gz
ignore_nan #63ecma-262-63
Diffstat (limited to 'simplejson')
-rw-r--r--simplejson/__init__.py49
-rw-r--r--simplejson/_speedups.c35
-rw-r--r--simplejson/encoder.py16
-rw-r--r--simplejson/tests/test_float.py10
4 files changed, 76 insertions, 34 deletions
diff --git a/simplejson/__init__.py b/simplejson/__init__.py
index a1095c5..37a9e52 100644
--- a/simplejson/__init__.py
+++ b/simplejson/__init__.py
@@ -98,7 +98,7 @@ Using simplejson.tool from the shell to validate and pretty-print::
Expecting property name: line 1 column 3 (char 2)
"""
from __future__ import absolute_import
-__version__ = '3.1.3'
+__version__ = '3.2.0'
__all__ = [
'dump', 'dumps', 'load', 'loads',
'JSONDecoder', 'JSONDecodeError', 'JSONEncoder',
@@ -143,6 +143,7 @@ _default_encoder = JSONEncoder(
bigint_as_string=False,
item_sort_key=None,
for_json=False,
+ ignore_nan=False,
)
def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
@@ -150,28 +151,29 @@ def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
encoding='utf-8', default=None, use_decimal=True,
namedtuple_as_object=True, tuple_as_array=True,
bigint_as_string=False, sort_keys=False, item_sort_key=None,
- for_json=False, **kw):
+ for_json=False, ignore_nan=False, **kw):
"""Serialize ``obj`` as a JSON formatted stream to ``fp`` (a
``.write()``-supporting file-like object).
- If ``skipkeys`` is true then ``dict`` keys that are not basic types
+ If *skipkeys* is true then ``dict`` keys that are not basic types
(``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
will be skipped instead of raising a ``TypeError``.
- If ``ensure_ascii`` is false, then the some chunks written to ``fp``
+ If *ensure_ascii* is false, then the some chunks written to ``fp``
may be ``unicode`` instances, subject to normal Python ``str`` to
``unicode`` coercion rules. Unless ``fp.write()`` explicitly
understands ``unicode`` (as in ``codecs.getwriter()``) this is likely
to cause an error.
- If ``check_circular`` is false, then the circular reference check
+ If *check_circular* is false, then the circular reference check
for container types will be skipped and a circular reference will
result in an ``OverflowError`` (or worse).
- If ``allow_nan`` is false, then it will be a ``ValueError`` to
+ If *allow_nan* is false, then it will be a ``ValueError`` to
serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``)
- in strict compliance of the JSON specification, instead of using the
- JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
+ in strict compliance of the original JSON specification, instead of using
+ the JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``). See
+ *ignore_nan* for ECMA-262 compliant behavior.
If *indent* is a string, then JSON array elements and object members
will be pretty-printed with a newline followed by that string repeated
@@ -180,16 +182,16 @@ def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
versions of simplejson earlier than 2.1.0, an integer is also accepted
and is converted to a string with that many spaces.
- If specified, ``separators`` should be an
+ If specified, *separators* should be an
``(item_separator, key_separator)`` tuple. The default is ``(', ', ': ')``
if *indent* is ``None`` and ``(',', ': ')`` otherwise. To get the most
compact JSON representation, you should specify ``(',', ':')`` to eliminate
whitespace.
- ``encoding`` is the character encoding for str instances, default is UTF-8.
+ *encoding* is the character encoding for str instances, default is UTF-8.
- ``default(obj)`` is a function that should return a serializable version
- of obj or raise TypeError. The default simply raises TypeError.
+ *default(obj)* is a function that should return a serializable version
+ of obj or raise ``TypeError``. The default simply raises ``TypeError``.
If *use_decimal* is true (default: ``True``) then decimal.Decimal
will be natively serialized to JSON with full precision.
@@ -219,10 +221,15 @@ def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
method will use the return value of that method for encoding as JSON
instead of the object.
+ If *ignore_nan* is true (default: ``False``), then out of range
+ :class:`float` values (``nan``, ``inf``, ``-inf``) will be serialized as
+ ``null`` in compliance with the ECMA-262 specification. If true, this will
+ override *allow_nan*.
+
To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
``.default()`` method to serialize additional types), specify it with
- the ``cls`` kwarg. NOTE: You should use *default* instead of subclassing
- whenever possible.
+ the ``cls`` kwarg. NOTE: You should use *default* or *for_json* instead
+ of subclassing whenever possible.
"""
# cached encoder
@@ -232,7 +239,7 @@ def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
encoding == 'utf-8' and default is None and use_decimal
and namedtuple_as_object and tuple_as_array
and not bigint_as_string and not item_sort_key
- and not for_json and not kw):
+ and not for_json and not ignore_nan and not kw):
iterable = _default_encoder.iterencode(obj)
else:
if cls is None:
@@ -247,6 +254,7 @@ def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
sort_keys=sort_keys,
item_sort_key=item_sort_key,
for_json=for_json,
+ ignore_nan=ignore_nan,
**kw).iterencode(obj)
# could accelerate with writelines in some versions of Python, at
# a debuggability cost
@@ -259,7 +267,7 @@ def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
encoding='utf-8', default=None, use_decimal=True,
namedtuple_as_object=True, tuple_as_array=True,
bigint_as_string=False, sort_keys=False, item_sort_key=None,
- for_json=False, **kw):
+ for_json=False, ignore_nan=False, **kw):
"""Serialize ``obj`` to a JSON formatted ``str``.
If ``skipkeys`` is false then ``dict`` keys that are not basic types
@@ -323,6 +331,11 @@ def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
method will use the return value of that method for encoding as JSON
instead of the object.
+ If *ignore_nan* is true (default: ``False``), then out of range
+ :class:`float` values (``nan``, ``inf``, ``-inf``) will be serialized as
+ ``null`` in compliance with the ECMA-262 specification. If true, this will
+ override *allow_nan*.
+
To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
``.default()`` method to serialize additional types), specify it with
the ``cls`` kwarg. NOTE: You should use *default* instead of subclassing
@@ -336,7 +349,8 @@ def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
encoding == 'utf-8' and default is None and use_decimal
and namedtuple_as_object and tuple_as_array
and not bigint_as_string and not sort_keys
- and not item_sort_key and not for_json and not kw):
+ and not item_sort_key and not for_json
+ and not ignore_nan and not kw):
return _default_encoder.encode(obj)
if cls is None:
cls = JSONEncoder
@@ -351,6 +365,7 @@ def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
sort_keys=sort_keys,
item_sort_key=item_sort_key,
for_json=for_json,
+ ignore_nan=ignore_nan,
**kw).encode(obj)
diff --git a/simplejson/_speedups.c b/simplejson/_speedups.c
index c698130..93f136c 100644
--- a/simplejson/_speedups.c
+++ b/simplejson/_speedups.c
@@ -93,6 +93,9 @@ typedef int Py_ssize_t;
#define PyEncoder_Check(op) PyObject_TypeCheck(op, &PyEncoderType)
#define PyEncoder_CheckExact(op) (Py_TYPE(op) == &PyEncoderType)
+#define JSON_ALLOW_NAN 1
+#define JSON_IGNORE_NAN 2
+
static PyTypeObject PyScannerType;
static PyTypeObject PyEncoderType;
@@ -163,7 +166,8 @@ typedef struct _PyEncoderObject {
PyObject *skipkeys_bool;
int skipkeys;
int fast_encode;
- int allow_nan;
+ /* 0, JSON_ALLOW_NAN, JSON_IGNORE_NAN */
+ int allow_or_ignore_nan;
int use_decimal;
int namedtuple_as_object;
int tuple_as_array;
@@ -725,11 +729,11 @@ encoder_dict_iteritems(PyEncoderObject *s, PyObject *dct)
goto bail;
#if PY_MAJOR_VERSION < 3
else if (PyString_Check(key)) {
- // item can be added as-is
+ /* item can be added as-is */
}
#endif /* PY_MAJOR_VERSION < 3 */
else if (PyUnicode_Check(key)) {
- // item can be added as-is
+ /* item can be added as-is */
}
else {
PyObject *tpl;
@@ -737,7 +741,7 @@ encoder_dict_iteritems(PyEncoderObject *s, PyObject *dct)
if (kstr == NULL)
goto bail;
else if (kstr == Py_None) {
- // skipkeys
+ /* skipkeys */
Py_DECREF(kstr);
continue;
}
@@ -2588,22 +2592,23 @@ static int
encoder_init(PyObject *self, PyObject *args, PyObject *kwds)
{
/* initialize Encoder object */
- static char *kwlist[] = {"markers", "default", "encoder", "indent", "key_separator", "item_separator", "sort_keys", "skipkeys", "allow_nan", "key_memo", "use_decimal", "namedtuple_as_object", "tuple_as_array", "bigint_as_string", "item_sort_key", "encoding", "for_json", "Decimal", NULL};
+ static char *kwlist[] = {"markers", "default", "encoder", "indent", "key_separator", "item_separator", "sort_keys", "skipkeys", "allow_nan", "key_memo", "use_decimal", "namedtuple_as_object", "tuple_as_array", "bigint_as_string", "item_sort_key", "encoding", "for_json", "ignore_nan", "Decimal", NULL};
PyEncoderObject *s;
PyObject *markers, *defaultfn, *encoder, *indent, *key_separator;
PyObject *item_separator, *sort_keys, *skipkeys, *allow_nan, *key_memo;
PyObject *use_decimal, *namedtuple_as_object, *tuple_as_array;
- PyObject *bigint_as_string, *item_sort_key, *encoding, *Decimal, *for_json;
+ PyObject *bigint_as_string, *item_sort_key, *encoding, *for_json;
+ PyObject *ignore_nan, *Decimal;
assert(PyEncoder_Check(self));
s = (PyEncoderObject *)self;
- if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOOOOOOOOOOOOOOOOO:make_encoder", kwlist,
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOOOOOOOOOOOOOOOOOO:make_encoder", kwlist,
&markers, &defaultfn, &encoder, &indent, &key_separator, &item_separator,
&sort_keys, &skipkeys, &allow_nan, &key_memo, &use_decimal,
&namedtuple_as_object, &tuple_as_array, &bigint_as_string,
- &item_sort_key, &encoding, &for_json, &Decimal))
+ &item_sort_key, &encoding, &for_json, &ignore_nan, &Decimal))
return -1;
s->markers = markers;
@@ -2619,7 +2624,9 @@ encoder_init(PyObject *self, PyObject *args, PyObject *kwds)
s->skipkeys = PyObject_IsTrue(skipkeys);
s->key_memo = key_memo;
s->fast_encode = (PyCFunction_Check(s->encoder) && PyCFunction_GetFunction(s->encoder) == (PyCFunction)py_encode_basestring_ascii);
- s->allow_nan = PyObject_IsTrue(allow_nan);
+ s->allow_or_ignore_nan = (
+ (PyObject_IsTrue(ignore_nan) ? JSON_IGNORE_NAN : 0) |
+ (PyObject_IsTrue(allow_nan) ? JSON_ALLOW_NAN : 0));
s->use_decimal = PyObject_IsTrue(use_decimal);
s->namedtuple_as_object = PyObject_IsTrue(namedtuple_as_object);
s->tuple_as_array = PyObject_IsTrue(tuple_as_array);
@@ -2734,11 +2741,15 @@ encoder_encode_float(PyEncoderObject *s, PyObject *obj)
/* Return the JSON representation of a PyFloat */
double i = PyFloat_AS_DOUBLE(obj);
if (!Py_IS_FINITE(i)) {
- if (!s->allow_nan) {
+ if (!s->allow_or_ignore_nan) {
PyErr_SetString(PyExc_ValueError, "Out of range float values are not JSON compliant");
return NULL;
}
- if (i > 0) {
+ if (s->allow_or_ignore_nan & JSON_IGNORE_NAN) {
+ return _encoded_const(Py_None);
+ }
+ /* JSON_ALLOW_NAN is set */
+ else if (i > 0) {
static PyObject *sInfinity = NULL;
if (sInfinity == NULL)
sInfinity = JSON_InternFromString("Infinity");
@@ -2985,7 +2996,7 @@ encoder_listencode_dict(PyEncoderObject *s, JSON_Accu *rval, PyObject *dct, Py_s
if (kstr == NULL)
goto bail;
else if (kstr == Py_None) {
- // skipkeys
+ /* skipkeys */
Py_DECREF(item);
Py_DECREF(kstr);
continue;
diff --git a/simplejson/encoder.py b/simplejson/encoder.py
index f2acdd3..9815ee5 100644
--- a/simplejson/encoder.py
+++ b/simplejson/encoder.py
@@ -121,7 +121,7 @@ class JSONEncoder(object):
indent=None, separators=None, encoding='utf-8', default=None,
use_decimal=True, namedtuple_as_object=True,
tuple_as_array=True, bigint_as_string=False,
- item_sort_key=None, for_json=False):
+ item_sort_key=None, for_json=False, ignore_nan=False):
"""Constructor for JSONEncoder, with sensible defaults.
If skipkeys is false, then it is a TypeError to attempt
@@ -188,6 +188,11 @@ class JSONEncoder(object):
method will use the return value of that method for encoding as JSON
instead of the object.
+ If *ignore_nan* is true (default: ``False``), then out of range
+ :class:`float` values (``nan``, ``inf``, ``-inf``) will be serialized
+ as ``null`` in compliance with the ECMA-262 specification. If true,
+ this will override *allow_nan*.
+
"""
self.skipkeys = skipkeys
@@ -201,6 +206,7 @@ class JSONEncoder(object):
self.bigint_as_string = bigint_as_string
self.item_sort_key = item_sort_key
self.for_json = for_json
+ self.ignore_nan = ignore_nan
if indent is not None and not isinstance(indent, string_types):
indent = indent * ' '
self.indent = indent
@@ -285,7 +291,7 @@ class JSONEncoder(object):
o = o.decode(_encoding)
return _orig_encoder(o)
- def floatstr(o, allow_nan=self.allow_nan,
+ def floatstr(o, allow_nan=self.allow_nan, ignore_nan=self.ignore_nan,
_repr=FLOAT_REPR, _inf=PosInf, _neginf=-PosInf):
# Check for specials. Note that this type of test is processor
# and/or platform-specific, so do tests which don't depend on
@@ -300,7 +306,9 @@ class JSONEncoder(object):
else:
return _repr(o)
- if not allow_nan:
+ if ignore_nan:
+ text = 'null'
+ elif not allow_nan:
raise ValueError(
"Out of range float values are not JSON compliant: " +
repr(o))
@@ -317,7 +325,7 @@ class JSONEncoder(object):
self.skipkeys, self.allow_nan, key_memo, self.use_decimal,
self.namedtuple_as_object, self.tuple_as_array,
self.bigint_as_string, self.item_sort_key,
- self.encoding, self.for_json,
+ self.encoding, self.for_json, self.ignore_nan,
Decimal)
else:
_iterencode = _make_iterencode(
diff --git a/simplejson/tests/test_float.py b/simplejson/tests/test_float.py
index f61ed68..e382ec2 100644
--- a/simplejson/tests/test_float.py
+++ b/simplejson/tests/test_float.py
@@ -5,13 +5,21 @@ import simplejson as json
from simplejson.decoder import NaN, PosInf, NegInf
class TestFloat(TestCase):
- def test_degenerates(self):
+ def test_degenerates_allow(self):
for inf in (PosInf, NegInf):
self.assertEqual(json.loads(json.dumps(inf)), inf)
# Python 2.5 doesn't have math.isnan
nan = json.loads(json.dumps(NaN))
self.assertTrue((0 + nan) != nan)
+ def test_degenerates_ignore(self):
+ for f in (PosInf, NegInf, NaN):
+ self.assertEqual(json.loads(json.dumps(f, ignore_nan=True)), None)
+
+ def test_degenerates_deny(self):
+ for f in (PosInf, NegInf, NaN):
+ self.assertRaises(ValueError, json.dumps, f, allow_nan=False)
+
def test_floats(self):
for num in [1617161771.7650001, math.pi, math.pi**100,
math.pi**-100, 3.1]: