Skip to content
Snippets Groups Projects
Commit 46a2e2f798a0 authored by Bob Ippolito's avatar Bob Ippolito
Browse files

Merge remote-tracking branch 'scottkmaxwell/master' into item_sort_key

Branches
No related tags found
No related merge requests found
...@@ -101,7 +101,7 @@ ...@@ -101,7 +101,7 @@
__all__ = [ __all__ = [
'dump', 'dumps', 'load', 'loads', 'dump', 'dumps', 'load', 'loads',
'JSONDecoder', 'JSONDecodeError', 'JSONEncoder', 'JSONDecoder', 'JSONDecodeError', 'JSONEncoder',
'OrderedDict', 'OrderedDict', 'simple_first'
] ]
__author__ = 'Bob Ippolito <bob@redivi.com>' __author__ = 'Bob Ippolito <bob@redivi.com>'
...@@ -139,9 +139,10 @@ ...@@ -139,9 +139,10 @@
namedtuple_as_object=True, namedtuple_as_object=True,
tuple_as_array=True, tuple_as_array=True,
bigint_as_string=False, bigint_as_string=False,
item_sort_key=None,
) )
def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True, def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
allow_nan=True, cls=None, indent=None, separators=None, allow_nan=True, cls=None, indent=None, separators=None,
encoding='utf-8', default=None, use_decimal=True, encoding='utf-8', default=None, use_decimal=True,
namedtuple_as_object=True, tuple_as_array=True, namedtuple_as_object=True, tuple_as_array=True,
...@@ -142,10 +143,10 @@ ...@@ -142,10 +143,10 @@
) )
def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True, def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
allow_nan=True, cls=None, indent=None, separators=None, allow_nan=True, cls=None, indent=None, separators=None,
encoding='utf-8', default=None, use_decimal=True, encoding='utf-8', default=None, use_decimal=True,
namedtuple_as_object=True, tuple_as_array=True, namedtuple_as_object=True, tuple_as_array=True,
bigint_as_string=False, **kw): bigint_as_string=False, item_sort_key=None, **kw):
"""Serialize ``obj`` as a JSON formatted stream to ``fp`` (a """Serialize ``obj`` as a JSON formatted stream to ``fp`` (a
``.write()``-supporting file-like object). ``.write()``-supporting file-like object).
...@@ -200,6 +201,10 @@ ...@@ -200,6 +201,10 @@
lossy operation that will not round-trip correctly and should be used lossy operation that will not round-trip correctly and should be used
sparingly. sparingly.
If specified, *item_sort_key* is a callable used to sort the items in
each dictionary. This is useful if you want to sort items other than
in alphabetical order by key.
To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
``.default()`` method to serialize additional types), specify it with ``.default()`` method to serialize additional types), specify it with
the ``cls`` kwarg. the ``cls`` kwarg.
...@@ -211,7 +216,7 @@ ...@@ -211,7 +216,7 @@
cls is None and indent is None and separators is None and cls is None and indent is None and separators is None and
encoding == 'utf-8' and default is None and use_decimal encoding == 'utf-8' and default is None and use_decimal
and namedtuple_as_object and tuple_as_array and namedtuple_as_object and tuple_as_array
and not bigint_as_string and not kw): and not bigint_as_string and not item_sort_key and not kw):
iterable = _default_encoder.iterencode(obj) iterable = _default_encoder.iterencode(obj)
else: else:
if cls is None: if cls is None:
...@@ -223,6 +228,7 @@ ...@@ -223,6 +228,7 @@
namedtuple_as_object=namedtuple_as_object, namedtuple_as_object=namedtuple_as_object,
tuple_as_array=tuple_as_array, tuple_as_array=tuple_as_array,
bigint_as_string=bigint_as_string, bigint_as_string=bigint_as_string,
item_sort_key=item_sort_key,
**kw).iterencode(obj) **kw).iterencode(obj)
# could accelerate with writelines in some versions of Python, at # could accelerate with writelines in some versions of Python, at
# a debuggability cost # a debuggability cost
...@@ -233,8 +239,8 @@ ...@@ -233,8 +239,8 @@
def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True, def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
allow_nan=True, cls=None, indent=None, separators=None, allow_nan=True, cls=None, indent=None, separators=None,
encoding='utf-8', default=None, use_decimal=True, encoding='utf-8', default=None, use_decimal=True,
namedtuple_as_object=True, namedtuple_as_object=True, tuple_as_array=True,
tuple_as_array=True, bigint_as_string=False, bigint_as_string=False, item_sort_key=None,
**kw): **kw):
"""Serialize ``obj`` to a JSON formatted ``str``. """Serialize ``obj`` to a JSON formatted ``str``.
...@@ -285,6 +291,10 @@ ...@@ -285,6 +291,10 @@
or lower than -2**53 will be encoded as strings. This is to avoid the or lower than -2**53 will be encoded as strings. This is to avoid the
rounding that happens in Javascript otherwise. rounding that happens in Javascript otherwise.
If specified, *item_sort_key* is a callable used to sort the items in
each dictionary. This is useful if you want to sort items other than
in alphabetical order by key.
To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
``.default()`` method to serialize additional types), specify it with ``.default()`` method to serialize additional types), specify it with
the ``cls`` kwarg. the ``cls`` kwarg.
...@@ -296,7 +306,7 @@ ...@@ -296,7 +306,7 @@
cls is None and indent is None and separators is None and cls is None and indent is None and separators is None and
encoding == 'utf-8' and default is None and use_decimal encoding == 'utf-8' and default is None and use_decimal
and namedtuple_as_object and tuple_as_array and namedtuple_as_object and tuple_as_array
and not bigint_as_string and not kw): and not bigint_as_string and not item_sort_key and not kw):
return _default_encoder.encode(obj) return _default_encoder.encode(obj)
if cls is None: if cls is None:
cls = JSONEncoder cls = JSONEncoder
...@@ -308,6 +318,7 @@ ...@@ -308,6 +318,7 @@
namedtuple_as_object=namedtuple_as_object, namedtuple_as_object=namedtuple_as_object,
tuple_as_array=tuple_as_array, tuple_as_array=tuple_as_array,
bigint_as_string=bigint_as_string, bigint_as_string=bigint_as_string,
item_sort_key=item_sort_key,
**kw).encode(obj) **kw).encode(obj)
...@@ -479,3 +490,9 @@ ...@@ -479,3 +490,9 @@
encoding='utf-8', encoding='utf-8',
default=None, default=None,
) )
def simple_first(kv):
"""Helper function to pass to item_sort_key to sort simple
elements to the top, then container elements.
"""
return isinstance(kv[1],(list,dict,tuple)), kv[0]
...@@ -90,6 +90,7 @@ ...@@ -90,6 +90,7 @@
int namedtuple_as_object; int namedtuple_as_object;
int tuple_as_array; int tuple_as_array;
int bigint_as_string; int bigint_as_string;
PyObject *item_sort_key;
} PyEncoderObject; } PyEncoderObject;
static PyMemberDef encoder_members[] = { static PyMemberDef encoder_members[] = {
...@@ -102,6 +103,7 @@ ...@@ -102,6 +103,7 @@
{"sort_keys", T_OBJECT, offsetof(PyEncoderObject, sort_keys), READONLY, "sort_keys"}, {"sort_keys", T_OBJECT, offsetof(PyEncoderObject, sort_keys), READONLY, "sort_keys"},
{"skipkeys", T_OBJECT, offsetof(PyEncoderObject, skipkeys), READONLY, "skipkeys"}, {"skipkeys", T_OBJECT, offsetof(PyEncoderObject, skipkeys), READONLY, "skipkeys"},
{"key_memo", T_OBJECT, offsetof(PyEncoderObject, key_memo), READONLY, "key_memo"}, {"key_memo", T_OBJECT, offsetof(PyEncoderObject, key_memo), READONLY, "key_memo"},
{"item_sort_key", T_OBJECT, offsetof(PyEncoderObject, item_sort_key), READONLY, "item_sort_key"},
{NULL} {NULL}
}; };
...@@ -2050,6 +2052,7 @@ ...@@ -2050,6 +2052,7 @@
s->sort_keys = NULL; s->sort_keys = NULL;
s->skipkeys = NULL; s->skipkeys = NULL;
s->key_memo = NULL; s->key_memo = NULL;
s->item_sort_key = NULL;
} }
return (PyObject *)s; return (PyObject *)s;
} }
...@@ -2058,7 +2061,7 @@ ...@@ -2058,7 +2061,7 @@
encoder_init(PyObject *self, PyObject *args, PyObject *kwds) encoder_init(PyObject *self, PyObject *args, PyObject *kwds)
{ {
/* initialize Encoder object */ /* 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", 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", NULL};
PyEncoderObject *s; PyEncoderObject *s;
PyObject *markers, *defaultfn, *encoder, *indent, *key_separator; PyObject *markers, *defaultfn, *encoder, *indent, *key_separator;
...@@ -2062,8 +2065,8 @@ ...@@ -2062,8 +2065,8 @@
PyEncoderObject *s; PyEncoderObject *s;
PyObject *markers, *defaultfn, *encoder, *indent, *key_separator; 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, *bigint_as_string; PyObject *item_separator, *sort_keys, *skipkeys, *allow_nan, *key_memo, *use_decimal, *namedtuple_as_object, *tuple_as_array, *bigint_as_string, *item_sort_key;
assert(PyEncoder_Check(self)); assert(PyEncoder_Check(self));
s = (PyEncoderObject *)self; s = (PyEncoderObject *)self;
...@@ -2066,7 +2069,7 @@ ...@@ -2066,7 +2069,7 @@
assert(PyEncoder_Check(self)); assert(PyEncoder_Check(self));
s = (PyEncoderObject *)self; s = (PyEncoderObject *)self;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOOOOOOOOOOOOO:make_encoder", kwlist, if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOOOOOOOOOOOOOO:make_encoder", kwlist,
&markers, &defaultfn, &encoder, &indent, &key_separator, &item_separator, &markers, &defaultfn, &encoder, &indent, &key_separator, &item_separator,
&sort_keys, &skipkeys, &allow_nan, &key_memo, &use_decimal, &sort_keys, &skipkeys, &allow_nan, &key_memo, &use_decimal,
...@@ -2071,6 +2074,6 @@ ...@@ -2071,6 +2074,6 @@
&markers, &defaultfn, &encoder, &indent, &key_separator, &item_separator, &markers, &defaultfn, &encoder, &indent, &key_separator, &item_separator,
&sort_keys, &skipkeys, &allow_nan, &key_memo, &use_decimal, &sort_keys, &skipkeys, &allow_nan, &key_memo, &use_decimal,
&namedtuple_as_object, &tuple_as_array, &bigint_as_string)) &namedtuple_as_object, &tuple_as_array, &bigint_as_string, &item_sort_key))
return -1; return -1;
s->markers = markers; s->markers = markers;
...@@ -2088,6 +2091,7 @@ ...@@ -2088,6 +2091,7 @@
s->namedtuple_as_object = PyObject_IsTrue(namedtuple_as_object); s->namedtuple_as_object = PyObject_IsTrue(namedtuple_as_object);
s->tuple_as_array = PyObject_IsTrue(tuple_as_array); s->tuple_as_array = PyObject_IsTrue(tuple_as_array);
s->bigint_as_string = PyObject_IsTrue(bigint_as_string); s->bigint_as_string = PyObject_IsTrue(bigint_as_string);
s->item_sort_key = item_sort_key;
Py_INCREF(s->markers); Py_INCREF(s->markers);
Py_INCREF(s->defaultfn); Py_INCREF(s->defaultfn);
...@@ -2098,6 +2102,7 @@ ...@@ -2098,6 +2102,7 @@
Py_INCREF(s->sort_keys); Py_INCREF(s->sort_keys);
Py_INCREF(s->skipkeys); Py_INCREF(s->skipkeys);
Py_INCREF(s->key_memo); Py_INCREF(s->key_memo);
Py_INCREF(s->item_sort_key);
return 0; return 0;
} }
...@@ -2356,7 +2361,14 @@ ...@@ -2356,7 +2361,14 @@
*/ */
} }
if (PyObject_IsTrue(s->sort_keys)) { if (PyCallable_Check(s->item_sort_key)) {
if (PyDict_CheckExact(dct))
items = PyDict_Items(dct);
else
items = PyMapping_Items(dct);
PyObject_CallMethod(items, "sort", "OO", Py_None, s->item_sort_key);
}
else if (PyObject_IsTrue(s->sort_keys)) {
/* First sort the keys then replace them with (key, value) tuples. */ /* First sort the keys then replace them with (key, value) tuples. */
Py_ssize_t i, nitems; Py_ssize_t i, nitems;
if (PyDict_CheckExact(dct)) if (PyDict_CheckExact(dct))
...@@ -2616,6 +2628,7 @@ ...@@ -2616,6 +2628,7 @@
Py_VISIT(s->sort_keys); Py_VISIT(s->sort_keys);
Py_VISIT(s->skipkeys); Py_VISIT(s->skipkeys);
Py_VISIT(s->key_memo); Py_VISIT(s->key_memo);
Py_VISIT(s->item_sort_key);
return 0; return 0;
} }
...@@ -2635,6 +2648,7 @@ ...@@ -2635,6 +2648,7 @@
Py_CLEAR(s->sort_keys); Py_CLEAR(s->sort_keys);
Py_CLEAR(s->skipkeys); Py_CLEAR(s->skipkeys);
Py_CLEAR(s->key_memo); Py_CLEAR(s->key_memo);
Py_CLEAR(s->item_sort_key);
return 0; return 0;
} }
......
...@@ -107,7 +107,8 @@ ...@@ -107,7 +107,8 @@
check_circular=True, allow_nan=True, sort_keys=False, check_circular=True, allow_nan=True, sort_keys=False,
indent=None, separators=None, encoding='utf-8', default=None, indent=None, separators=None, encoding='utf-8', default=None,
use_decimal=True, namedtuple_as_object=True, use_decimal=True, namedtuple_as_object=True,
tuple_as_array=True, bigint_as_string=False): tuple_as_array=True, bigint_as_string=False,
item_sort_key=None):
"""Constructor for JSONEncoder, with sensible defaults. """Constructor for JSONEncoder, with sensible defaults.
If skipkeys is false, then it is a TypeError to attempt If skipkeys is false, then it is a TypeError to attempt
...@@ -164,6 +165,10 @@ ...@@ -164,6 +165,10 @@
If bigint_as_string is true (not the default), ints 2**53 and higher 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 or lower than -2**53 will be encoded as strings. This is to avoid the
rounding that happens in Javascript otherwise. rounding that happens in Javascript otherwise.
If specified, item_sort_key is a callable used to sort the items in
each dictionary. This is useful if you want to sort items other than
in alphabetical order by key.
""" """
self.skipkeys = skipkeys self.skipkeys = skipkeys
...@@ -175,6 +180,7 @@ ...@@ -175,6 +180,7 @@
self.namedtuple_as_object = namedtuple_as_object self.namedtuple_as_object = namedtuple_as_object
self.tuple_as_array = tuple_as_array self.tuple_as_array = tuple_as_array
self.bigint_as_string = bigint_as_string self.bigint_as_string = bigint_as_string
self.item_sort_key = item_sort_key
if indent is not None and not isinstance(indent, basestring): if indent is not None and not isinstance(indent, basestring):
indent = indent * ' ' indent = indent * ' '
self.indent = indent self.indent = indent
...@@ -291,10 +297,10 @@ ...@@ -291,10 +297,10 @@
self.key_separator, self.item_separator, self.sort_keys, self.key_separator, self.item_separator, self.sort_keys,
self.skipkeys, self.allow_nan, key_memo, self.use_decimal, self.skipkeys, self.allow_nan, key_memo, self.use_decimal,
self.namedtuple_as_object, self.tuple_as_array, self.namedtuple_as_object, self.tuple_as_array,
self.bigint_as_string) self.bigint_as_string, self.item_sort_key)
else: else:
_iterencode = _make_iterencode( _iterencode = _make_iterencode(
markers, self.default, _encoder, self.indent, floatstr, markers, self.default, _encoder, self.indent, floatstr,
self.key_separator, self.item_separator, self.sort_keys, self.key_separator, self.item_separator, self.sort_keys,
self.skipkeys, _one_shot, self.use_decimal, self.skipkeys, _one_shot, self.use_decimal,
self.namedtuple_as_object, self.tuple_as_array, self.namedtuple_as_object, self.tuple_as_array,
...@@ -295,10 +301,10 @@ ...@@ -295,10 +301,10 @@
else: else:
_iterencode = _make_iterencode( _iterencode = _make_iterencode(
markers, self.default, _encoder, self.indent, floatstr, markers, self.default, _encoder, self.indent, floatstr,
self.key_separator, self.item_separator, self.sort_keys, self.key_separator, self.item_separator, self.sort_keys,
self.skipkeys, _one_shot, self.use_decimal, self.skipkeys, _one_shot, self.use_decimal,
self.namedtuple_as_object, self.tuple_as_array, self.namedtuple_as_object, self.tuple_as_array,
self.bigint_as_string) self.bigint_as_string, self.item_sort_key)
try: try:
return _iterencode(o, 0) return _iterencode(o, 0)
finally: finally:
...@@ -335,7 +341,7 @@ ...@@ -335,7 +341,7 @@
def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
_key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot, _key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot,
_use_decimal, _namedtuple_as_object, _tuple_as_array, _use_decimal, _namedtuple_as_object, _tuple_as_array,
_bigint_as_string, _bigint_as_string, _item_sort_key,
## HACK: hand-optimized bytecode; turn globals into locals ## HACK: hand-optimized bytecode; turn globals into locals
False=False, False=False,
True=True, True=True,
...@@ -437,7 +443,10 @@ ...@@ -437,7 +443,10 @@
newline_indent = None newline_indent = None
item_separator = _item_separator item_separator = _item_separator
first = True first = True
if _sort_keys: if _item_sort_key:
items = dct.items()
items.sort(key=_item_sort_key)
elif _sort_keys:
items = dct.items() items = dct.items()
items.sort(key=lambda kv: kv[0]) items.sort(key=lambda kv: kv[0])
else: else:
......
from unittest import TestCase
import simplejson as json
from operator import itemgetter
class TestItemSortKey(TestCase):
def test_simple_first(self):
a={'a': 1, 'c': 5, 'jack': 'jill', 'pick': 'axe', 'array': [1, 5, 6, 9], 'tuple': (83, 12, 3), 'crate': 'dog', 'zeak': 'oh'}
self.assertEquals(
'{"a": 1, "c": 5, "crate": "dog", "jack": "jill", "pick": "axe", "zeak": "oh", "array": [1, 5, 6, 9], "tuple": [83, 12, 3]}',
json.dumps(a,item_sort_key=json.simple_first))
def test_case(self):
a={'a': 1, 'c': 5, 'Jack': 'jill', 'pick': 'axe', 'Array': [1, 5, 6, 9], 'tuple': (83, 12, 3), 'crate': 'dog', 'zeak': 'oh'}
self.assertEquals(
'{"Array": [1, 5, 6, 9], "Jack": "jill", "a": 1, "c": 5, "crate": "dog", "pick": "axe", "tuple": [83, 12, 3], "zeak": "oh"}',
json.dumps(a,item_sort_key=itemgetter(0)))
self.assertEquals(
'{"a": 1, "Array": [1, 5, 6, 9], "c": 5, "crate": "dog", "Jack": "jill", "pick": "axe", "tuple": [83, 12, 3], "zeak": "oh"}',
json.dumps(a,item_sort_key=lambda kv: kv[0].lower()))
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment