summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorScott Maxwell <scott@codecobblers.com>2012-03-02 21:12:58 -0800
committerScott Maxwell <scott@codecobblers.com>2012-03-02 21:12:58 -0800
commitbdd25021bd1958003ed96026eed66f161bb9fc70 (patch)
treef0cb146020708405ee5edf476c07058da6054263
parent3b1b1aec3cf5175c6218a376e01049963f8f973c (diff)
downloadsimplejson-bdd25021bd1958003ed96026eed66f161bb9fc70.tar.gz
Finalized bigint_as_string feature
Renamed javascript_safe_ints to bigint_as_string to match the similar parameter in PHP. Added unit tests. Fixed boundary of the bigint range. Added bigint_as_string to default encoder test.
-rw-r--r--simplejson/__init__.py24
-rw-r--r--simplejson/_speedups.c24
-rw-r--r--simplejson/encoder.py20
-rw-r--r--simplejson/tests/test_bigint_as_string.py41
4 files changed, 79 insertions, 30 deletions
diff --git a/simplejson/__init__.py b/simplejson/__init__.py
index dc1c03f..88eb02d 100644
--- a/simplejson/__init__.py
+++ b/simplejson/__init__.py
@@ -138,13 +138,13 @@ _default_encoder = JSONEncoder(
use_decimal=True,
namedtuple_as_object=True,
tuple_as_array=True,
- javascript_safe_ints=False
+ bigint_as_string=False
)
def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
allow_nan=True, cls=None, indent=None, separators=None,
encoding='utf-8', default=None, use_decimal=True,
- namedtuple_as_object=True, tuple_as_array=True, javascript_safe_ints=False,
+ namedtuple_as_object=True, tuple_as_array=True, bigint_as_string=False,
**kw):
"""Serialize ``obj`` as a JSON formatted stream to ``fp`` (a
``.write()``-supporting file-like object).
@@ -194,8 +194,8 @@ def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
If *tuple_as_array* is true (default: ``True``),
:class:`tuple` (and subclasses) will be encoded as JSON arrays.
- If javascript_safe_ints is true (not the default), ints 2**53 and higher
- or -2**53 and lower will be encoded as strings. This is to avoid the
+ If bigint_as_string is true (not the default), ints 2**53 and higher
+ or lower than -2**53 will be encoded as strings. This is to avoid the
rounding that happens in Javascript otherwise.
To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
@@ -208,7 +208,8 @@ def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
check_circular and allow_nan and
cls is None and indent is None and separators is None and
encoding == 'utf-8' and default is None and use_decimal
- and namedtuple_as_object and tuple_as_array and not kw):
+ and namedtuple_as_object and tuple_as_array
+ and not bigint_as_string and not kw):
iterable = _default_encoder.iterencode(obj)
else:
if cls is None:
@@ -219,7 +220,7 @@ def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
default=default, use_decimal=use_decimal,
namedtuple_as_object=namedtuple_as_object,
tuple_as_array=tuple_as_array,
- javascript_safe_ints=javascript_safe_ints,
+ bigint_as_string=bigint_as_string,
**kw).iterencode(obj)
# could accelerate with writelines in some versions of Python, at
# a debuggability cost
@@ -231,7 +232,7 @@ def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
allow_nan=True, cls=None, indent=None, separators=None,
encoding='utf-8', default=None, use_decimal=True,
namedtuple_as_object=True,
- tuple_as_array=True, javascript_safe_ints=False,
+ tuple_as_array=True, bigint_as_string=False,
**kw):
"""Serialize ``obj`` to a JSON formatted ``str``.
@@ -278,8 +279,8 @@ def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
If *tuple_as_array* is true (default: ``True``),
:class:`tuple` (and subclasses) will be encoded as JSON arrays.
- If javascript_safe_ints is true (not the default), ints 2**53 and higher
- or -2**53 and lower will be encoded as strings. This is to avoid the
+ If bigint_as_string is true (not the default), ints 2**53 and higher
+ or lower than -2**53 will be encoded as strings. This is to avoid the
rounding that happens in Javascript otherwise.
To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
@@ -292,7 +293,8 @@ def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
check_circular and allow_nan and
cls is None and indent is None and separators is None and
encoding == 'utf-8' and default is None and use_decimal
- and namedtuple_as_object and tuple_as_array and not kw):
+ and namedtuple_as_object and tuple_as_array
+ and not bigint_as_string and not kw):
return _default_encoder.encode(obj)
if cls is None:
cls = JSONEncoder
@@ -303,7 +305,7 @@ def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
use_decimal=use_decimal,
namedtuple_as_object=namedtuple_as_object,
tuple_as_array=tuple_as_array,
- javascript_safe_ints=javascript_safe_ints,
+ bigint_as_string=bigint_as_string,
**kw).encode(obj)
diff --git a/simplejson/_speedups.c b/simplejson/_speedups.c
index 335b6a4..4efccdc 100644
--- a/simplejson/_speedups.c
+++ b/simplejson/_speedups.c
@@ -89,7 +89,7 @@ typedef struct _PyEncoderObject {
int use_decimal;
int namedtuple_as_object;
int tuple_as_array;
- int javascript_safe_ints;
+ int bigint_as_string;
} PyEncoderObject;
static PyMemberDef encoder_members[] = {
@@ -2026,11 +2026,11 @@ 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", "javascript_safe_ints", 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", NULL};
PyEncoderObject *s;
PyObject *markers, *defaultfn, *encoder, *indent, *key_separator;
- PyObject *item_separator, *sort_keys, *skipkeys, *allow_nan, *key_memo, *use_decimal, *namedtuple_as_object, *tuple_as_array, *javascript_safe_ints;
+ PyObject *item_separator, *sort_keys, *skipkeys, *allow_nan, *key_memo, *use_decimal, *namedtuple_as_object, *tuple_as_array, *bigint_as_string;
assert(PyEncoder_Check(self));
s = (PyEncoderObject *)self;
@@ -2038,7 +2038,7 @@ encoder_init(PyObject *self, PyObject *args, PyObject *kwds)
if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOOOOOOOOOOOOO: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, &javascript_safe_ints))
+ &namedtuple_as_object, &tuple_as_array, &bigint_as_string))
return -1;
s->markers = markers;
@@ -2055,7 +2055,7 @@ encoder_init(PyObject *self, PyObject *args, PyObject *kwds)
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);
- s->javascript_safe_ints = PyObject_IsTrue(javascript_safe_ints);
+ s->bigint_as_string = PyObject_IsTrue(bigint_as_string);
Py_INCREF(s->markers);
Py_INCREF(s->defaultfn);
@@ -2192,10 +2192,13 @@ encoder_listencode_obj(PyEncoderObject *s, PyObject *rval, PyObject *obj, Py_ssi
else if (PyInt_Check(obj) || PyLong_Check(obj)) {
PyObject *encoded = PyObject_Str(obj);
if (encoded != NULL) {
- if (s->javascript_safe_ints) {
+ if (s->bigint_as_string) {
int overflow;
PY_LONG_LONG value = PyLong_AsLongLongAndOverflow(obj, &overflow);
- if (overflow || (value>0 && (value>>53)) || (value<0 && ((-value)>>53))) {
+ if (value < 0) {
+ value = ~value;
+ }
+ if (overflow || (value>>53)) {
PyObject* quoted = PyString_FromFormat("\"%s\"", PyString_AsString(encoded));
Py_DECREF(encoded);
encoded = quoted;
@@ -2407,10 +2410,13 @@ encoder_listencode_dict(PyEncoderObject *s, PyObject *rval, PyObject *dct, Py_ss
kstr = PyObject_Str(key);
if (kstr == NULL)
goto bail;
- if (s->javascript_safe_ints) {
+ if (s->bigint_as_string) {
int overflow;
PY_LONG_LONG value = PyLong_AsLongLongAndOverflow(kstr, &overflow);
- if (overflow || (value>0 && (value>>53)) || (value<0 && ((-value)>>53))) {
+ if (value < 0) {
+ value = ~value;
+ }
+ if (overflow || (value>>53)) {
PyObject* quoted = PyString_FromFormat("\"%s\"", PyString_AsString(kstr));
Py_DECREF(kstr);
kstr = quoted;
diff --git a/simplejson/encoder.py b/simplejson/encoder.py
index a6872da..4c7601b 100644
--- a/simplejson/encoder.py
+++ b/simplejson/encoder.py
@@ -107,7 +107,7 @@ class JSONEncoder(object):
check_circular=True, allow_nan=True, sort_keys=False,
indent=None, separators=None, encoding='utf-8', default=None,
use_decimal=True, namedtuple_as_object=True,
- tuple_as_array=True, javascript_safe_ints=False):
+ tuple_as_array=True, bigint_as_string=False):
"""Constructor for JSONEncoder, with sensible defaults.
If skipkeys is false, then it is a TypeError to attempt
@@ -161,8 +161,8 @@ class JSONEncoder(object):
If tuple_as_array is true (the default), tuple (and subclasses) will
be encoded as JSON arrays.
- If javascript_safe_ints is true (not the default), ints 2**53 and higher
- or -2**53 and lower will be encoded as strings. This is to avoid the
+ If bigint_as_string is true (not the default), ints 2**53 and higher
+ or lower than -2**53 will be encoded as strings. This is to avoid the
rounding that happens in Javascript otherwise.
"""
@@ -174,7 +174,7 @@ class JSONEncoder(object):
self.use_decimal = use_decimal
self.namedtuple_as_object = namedtuple_as_object
self.tuple_as_array = tuple_as_array
- self.javascript_safe_ints = javascript_safe_ints
+ self.bigint_as_string = bigint_as_string
if indent is not None and not isinstance(indent, basestring):
indent = indent * ' '
self.indent = indent
@@ -290,13 +290,13 @@ class JSONEncoder(object):
markers, self.default, _encoder, self.indent,
self.key_separator, self.item_separator, self.sort_keys,
self.skipkeys, self.allow_nan, key_memo, self.use_decimal,
- self.namedtuple_as_object, self.tuple_as_array, self.javascript_safe_ints)
+ self.namedtuple_as_object, self.tuple_as_array, self.bigint_as_string)
else:
_iterencode = _make_iterencode(
markers, self.default, _encoder, self.indent, floatstr,
self.key_separator, self.item_separator, self.sort_keys,
self.skipkeys, _one_shot, self.use_decimal,
- self.namedtuple_as_object, self.tuple_as_array, self.javascript_safe_ints)
+ self.namedtuple_as_object, self.tuple_as_array, self.bigint_as_string)
try:
return _iterencode(o, 0)
finally:
@@ -332,7 +332,7 @@ class JSONEncoderForHTML(JSONEncoder):
def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
_key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot,
- _use_decimal, _namedtuple_as_object, _tuple_as_array, _javascript_safe_ints,
+ _use_decimal, _namedtuple_as_object, _tuple_as_array, _bigint_as_string,
## HACK: hand-optimized bytecode; turn globals into locals
False=False,
True=True,
@@ -383,7 +383,7 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
elif value is False:
yield buf + 'false'
elif isinstance(value, (int, long)):
- yield buf + str(value) if not _javascript_safe_ints or -2**53<value<2**53 else buf + '"' + str(value) + '"'
+ yield buf + str(value) if not _bigint_as_string or -(1<<53) <= value < (1<<53) else buf + '"' + str(value) + '"'
elif isinstance(value, float):
yield buf + _floatstr(value)
elif _use_decimal and isinstance(value, Decimal):
@@ -470,7 +470,7 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
elif value is False:
yield 'false'
elif isinstance(value, (int, long)):
- yield str(value) if not _javascript_safe_ints or -2**53<value<2**53 else '"' + str(value) + '"'
+ yield str(value) if not _bigint_as_string or -(1<<53) <= value < (1<<53) else '"' + str(value) + '"'
elif isinstance(value, float):
yield _floatstr(value)
elif _use_decimal and isinstance(value, Decimal):
@@ -508,7 +508,7 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
elif o is False:
yield 'false'
elif isinstance(o, (int, long)):
- yield str(o) if not _javascript_safe_ints or -2**53<value<2**53 else '"' + str(o) + '"'
+ yield str(o) if not _bigint_as_string or -(1<<53) <= o < (1<<53) else '"' + str(o) + '"'
elif isinstance(o, float):
yield _floatstr(o)
elif isinstance(o, list):
diff --git a/simplejson/tests/test_bigint_as_string.py b/simplejson/tests/test_bigint_as_string.py
new file mode 100644
index 0000000..b04aa1e
--- /dev/null
+++ b/simplejson/tests/test_bigint_as_string.py
@@ -0,0 +1,41 @@
+from unittest import TestCase
+
+import simplejson as json
+
+class TestBigintAsString(TestCase):
+ values = [(200, 200), (2**53-1, 9007199254740991), (2**53, '"9007199254740992"'), (2**53+1, '"9007199254740993"'), (-100, -100), (-2**53, -9007199254740992), (-2**53-1, '"-9007199254740993"'), (-2**53+1, -9007199254740991)]
+
+ def test_ints(self):
+ for value_pair in self.values:
+ self.assertEquals('%s' % value_pair[0], json.dumps(value_pair[0]))
+ self.assertEquals('%s' % value_pair[1], json.dumps(value_pair[0], bigint_as_string=True))
+
+ def test_lists(self):
+ for value_pair in self.values:
+ l = [value_pair[0], value_pair[0]]
+ self.assertEquals('[%s, %s]' % (value_pair[0], value_pair[0]), json.dumps(l))
+ self.assertEquals('[%s, %s]' % (value_pair[1], value_pair[1]), json.dumps(l, bigint_as_string=True))
+
+ def test_dicts(self):
+ for value_pair in self.values:
+ d = {'value': value_pair[0]}
+ self.assertEquals('{"value": %s}' % value_pair[0], json.dumps(d))
+ self.assertEquals('{"value": %s}' % value_pair[1], json.dumps(d, bigint_as_string=True))
+
+ def test_ints_without_speedups(self):
+ self.assertIsNotNone(json.encoder.c_make_encoder)
+ json._toggle_speedups(False)
+ self.test_ints()
+ json._toggle_speedups(True)
+
+ def test_lists_without_speedups(self):
+ self.assertIsNotNone(json.encoder.c_make_encoder)
+ json._toggle_speedups(False)
+ self.test_lists()
+ json._toggle_speedups(True)
+
+ def test_dicts_without_speedups(self):
+ self.assertIsNotNone(json.encoder.c_make_encoder)
+ json._toggle_speedups(False)
+ self.test_dicts()
+ json._toggle_speedups(True)