diff options
author | Scott Maxwell <scott@codecobblers.com> | 2012-03-02 21:12:58 -0800 |
---|---|---|
committer | Scott Maxwell <scott@codecobblers.com> | 2012-03-02 21:12:58 -0800 |
commit | bdd25021bd1958003ed96026eed66f161bb9fc70 (patch) | |
tree | f0cb146020708405ee5edf476c07058da6054263 | |
parent | 3b1b1aec3cf5175c6218a376e01049963f8f973c (diff) | |
download | simplejson-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__.py | 24 | ||||
-rw-r--r-- | simplejson/_speedups.c | 24 | ||||
-rw-r--r-- | simplejson/encoder.py | 20 | ||||
-rw-r--r-- | simplejson/tests/test_bigint_as_string.py | 41 |
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) |