Preserve order in json_util.dumps, PYTHON-602.

This commit is contained in:
A. Jesse Jiryu Davis 2013-12-13 17:01:52 -05:00
parent 4b5ba70724
commit 51deead4ff
2 changed files with 41 additions and 13 deletions

View File

@ -83,7 +83,7 @@ except ImportError:
json_lib = False
import bson
from bson import EPOCH_AWARE, RE_TYPE
from bson import EPOCH_AWARE, RE_TYPE, SON
from bson.binary import Binary
from bson.code import Code
from bson.dbref import DBRef
@ -111,6 +111,10 @@ def dumps(obj, *args, **kwargs):
Recursive function that handles all BSON types including
:class:`~bson.binary.Binary` and :class:`~bson.code.Code`.
.. versionchanged:: 2.7
Preserves order when rendering SON, Timestamp, Code, Binary, and DBRef
instances.
"""
if not json_lib:
raise Exception("No json library available")
@ -143,7 +147,7 @@ def _json_convert(obj):
converted into json.
"""
if hasattr(obj, 'iteritems') or hasattr(obj, 'items'): # PY3 support
return dict(((k, _json_convert(v)) for k, v in obj.iteritems()))
return SON(((k, _json_convert(v)) for k, v in obj.iteritems()))
elif hasattr(obj, '__iter__') and not isinstance(obj, string_types):
return list((_json_convert(v) for v in obj))
try:
@ -214,22 +218,23 @@ def default(obj):
flags += "u"
if obj.flags & re.VERBOSE:
flags += "x"
return {"$regex": obj.pattern,
"$options": flags}
return SON([("$regex", obj.pattern), ("$options", flags)])
if isinstance(obj, MinKey):
return {"$minKey": 1}
if isinstance(obj, MaxKey):
return {"$maxKey": 1}
if isinstance(obj, Timestamp):
return {"t": obj.time, "i": obj.inc}
return SON([("t", obj.time), ("i", obj.inc)])
if isinstance(obj, Code):
return {'$code': "%s" % obj, '$scope': obj.scope}
return SON([('$code', "%s" % obj), ('$scope', obj.scope)])
if isinstance(obj, Binary):
return {'$binary': base64.b64encode(obj).decode(),
'$type': "%02x" % obj.subtype}
return SON([
('$binary', base64.b64encode(obj).decode()),
('$type', "%02x" % obj.subtype)])
if PY3 and isinstance(obj, binary_type):
return {'$binary': base64.b64encode(obj).decode(),
'$type': "00"}
return SON([
('$binary', base64.b64encode(obj).decode()),
('$type', "00")])
if bson.has_uuid() and isinstance(obj, bson.uuid.UUID):
return {"$uuid": obj.hex}
raise TypeError("%r is not JSON serializable" % obj)

View File

@ -69,7 +69,11 @@ class TestJsonUtil(unittest.TestCase):
self.round_trip({"ref": DBRef("foo", 5)})
self.round_trip({"ref": DBRef("foo", 5, "db")})
self.round_trip({"ref": DBRef("foo", ObjectId())})
self.round_trip({"ref": DBRef("foo", ObjectId(), "db")})
# Check order.
self.assertEqual(
'{"$ref": "collection", "$id": 1, "$db": "db"}',
json_util.dumps(DBRef('collection', 1, 'db')))
def test_datetime(self):
# only millis, not micros
@ -126,6 +130,15 @@ class TestJsonUtil(unittest.TestCase):
'{"r": {"$regex": ".*", "$options": "ilm"}}',
compile_re=False)['r'])
# Check order.
self.assertEqual(
'{"$regex": ".*", "$options": "mx"}',
json_util.dumps(Regex('.*', re.M | re.X)))
self.assertEqual(
'{"$regex": ".*", "$options": "mx"}',
json_util.dumps(re.compile('.*', re.M | re.X)))
def test_minkey(self):
self.round_trip({"m": MinKey()})
@ -135,6 +148,7 @@ class TestJsonUtil(unittest.TestCase):
def test_timestamp(self):
res = json_util.json.dumps({"ts": Timestamp(4, 13)},
default=json_util.default)
self.assertEqual('{"ts": {"t": 4, "i": 13}}', res)
dct = json_util.json.loads(res)
self.assertEqual(dct['ts']['t'], 4)
self.assertEqual(dct['ts']['i'], 13)
@ -164,7 +178,10 @@ class TestJsonUtil(unittest.TestCase):
json_util.loads('{"bin": {"$type": 0, "$binary": "AAECAwQ="}}'))
json_bin_dump = json_util.dumps(md5_type_dict)
self.assertTrue('"$type": "05"' in json_bin_dump)
self.assertEqual(
'{"md5": {"$binary": "IG43GK8JL9HRL4DK53HMrA==", "$type": "05"}}',
json_bin_dump)
self.assertEqual(md5_type_dict,
json_util.loads('{"md5": {"$type": 5, "$binary":'
' "IG43GK8JL9HRL4DK53HMrA=="}}'))
@ -186,7 +203,13 @@ class TestJsonUtil(unittest.TestCase):
def test_code(self):
self.round_trip({"code": Code("function x() { return 1; }")})
self.round_trip({"code": Code("function y() { return z; }", z=2)})
code = Code("return z", z=2)
res = json_util.dumps(code)
self.assertEqual(code, json_util.loads(res))
# Check order.
self.assertEqual('{"$code": "return z", "$scope": {"z": 2}}', res)
def test_cursor(self):
db = self.db