From 2f67eff07eb66ae9a074cc8efe5bc3c9627e5dc6 Mon Sep 17 00:00:00 2001 From: "A. Jesse Jiryu Davis" Date: Sat, 14 Dec 2013 12:30:06 -0500 Subject: [PATCH] Note that we can't preserve key order in json_util.dumps in Python 2.4, PYTHON-602. --- bson/json_util.py | 14 +++++++++++- test/test_json_util.py | 49 +++++++++++++++++++++++++----------------- 2 files changed, 42 insertions(+), 21 deletions(-) diff --git a/bson/json_util.py b/bson/json_util.py index 8e2dd4b16..b7d46a5db 100644 --- a/bson/json_util.py +++ b/bson/json_util.py @@ -47,6 +47,10 @@ It won't handle :class:`~bson.binary.Binary` and :class:`~bson.code.Code` instances (as they are extended strings you can't provide custom defaults), but it will be faster as there is less recursion. +.. versionchanged:: 2.7 + Preserves order when rendering SON, Timestamp, Code, Binary, and DBRef + instances. (But not in Python 2.4.) + .. versionchanged:: 2.3 Added dumps and loads helpers to automatically handle conversion to and from json and supports :class:`~bson.binary.Binary` and @@ -114,7 +118,7 @@ def dumps(obj, *args, **kwargs): .. versionchanged:: 2.7 Preserves order when rendering SON, Timestamp, Code, Binary, and DBRef - instances. + instances. (But not in Python 2.4.) """ if not json_lib: raise Exception("No json library available") @@ -193,6 +197,14 @@ def object_hook(dct, compile_re=True): def default(obj): + # We preserve key order when rendering SON, DBRef, etc. as JSON by + # returning a SON for those types instead of a dict. This works with + # the "json" standard library in Python 2.6+ and with simplejson + # 2.1.0+ in Python 2.5+, because those libraries iterate the SON + # using PyIter_Next. Python 2.4 must use simplejson 2.0.9 or older, + # and those versions of simplejson use the lower-level PyDict_Next, + # which bypasses SON's order-preserving iteration, so we lose key + # order in Python 2.4. if isinstance(obj, ObjectId): return {"$oid": str(obj)} if isinstance(obj, DBRef): diff --git a/test/test_json_util.py b/test/test_json_util.py index 5ea83d38c..99ff9d49f 100644 --- a/test/test_json_util.py +++ b/test/test_json_util.py @@ -40,6 +40,7 @@ from bson.tz_util import utc from test.test_client import get_client PY3 = sys.version_info[0] == 3 +PY24 = sys.version_info[:2] == (2, 4) class TestJsonUtil(unittest.TestCase): @@ -70,10 +71,11 @@ class TestJsonUtil(unittest.TestCase): self.round_trip({"ref": DBRef("foo", 5, "db")}) self.round_trip({"ref": DBRef("foo", ObjectId())}) - # Check order. - self.assertEqual( - '{"$ref": "collection", "$id": 1, "$db": "db"}', - json_util.dumps(DBRef('collection', 1, 'db'))) + if not PY24: + # 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 @@ -130,14 +132,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))) + if not PY24: + # 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(b('.*'), re.M | re.X))) + self.assertEqual( + '{"$regex": ".*", "$options": "mx"}', + json_util.dumps(re.compile(b('.*'), re.M | re.X))) def test_minkey(self): self.round_trip({"m": MinKey()}) @@ -146,10 +149,12 @@ class TestJsonUtil(unittest.TestCase): self.round_trip({"m": MaxKey()}) 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) + res = json_util.dumps({"ts": Timestamp(4, 13)}, default=json_util.default) + if not PY24: + # Check order. + self.assertEqual('{"ts": {"t": 4, "i": 13}}', res) + + dct = json_util.loads(res) self.assertEqual(dct['ts']['t'], 4) self.assertEqual(dct['ts']['i'], 13) @@ -178,9 +183,12 @@ class TestJsonUtil(unittest.TestCase): json_util.loads('{"bin": {"$type": 0, "$binary": "AAECAwQ="}}')) json_bin_dump = json_util.dumps(md5_type_dict) - self.assertEqual( - '{"md5": {"$binary": "IG43GK8JL9HRL4DK53HMrA==", "$type": "05"}}', - json_bin_dump) + if not PY24: + # Check order. + self.assertEqual( + '{"md5": {"$binary": "IG43GK8JL9HRL4DK53HMrA==",' + + ' "$type": "05"}}', + json_bin_dump) self.assertEqual(md5_type_dict, json_util.loads('{"md5": {"$type": 5, "$binary":' @@ -208,8 +216,9 @@ class TestJsonUtil(unittest.TestCase): res = json_util.dumps(code) self.assertEqual(code, json_util.loads(res)) - # Check order. - self.assertEqual('{"$code": "return z", "$scope": {"z": 2}}', res) + if not PY24: + # Check order. + self.assertEqual('{"$code": "return z", "$scope": {"z": 2}}', res) def test_cursor(self): db = self.db