diff options
author | Bob Ippolito <bob@redivi.com> | 2013-05-01 13:00:06 -0700 |
---|---|---|
committer | Bob Ippolito <bob@redivi.com> | 2013-05-01 13:00:06 -0700 |
commit | 236c8a174c0e333608e992f6f7ec7826c2fd1cd8 (patch) | |
tree | 3a75f4ab4e8a7d6a1af1dd793a0cea99bf0a6fe0 /simplejson | |
parent | 928421f68ff2128c5397553a57f3d76a75e39ca4 (diff) | |
download | simplejson-236c8a174c0e333608e992f6f7ec7826c2fd1cd8.tar.gz |
ignore_nan #63ecma-262-63
Diffstat (limited to 'simplejson')
-rw-r--r-- | simplejson/__init__.py | 49 | ||||
-rw-r--r-- | simplejson/_speedups.c | 35 | ||||
-rw-r--r-- | simplejson/encoder.py | 16 | ||||
-rw-r--r-- | simplejson/tests/test_float.py | 10 |
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]: |