Skip to content

Commit 2f67eff

Browse files
author
A. Jesse Jiryu Davis
committed
Note that we can't preserve key order in json_util.dumps in Python 2.4, PYTHON-602.
1 parent c672b8c commit 2f67eff

File tree

2 files changed

+42
-21
lines changed

2 files changed

+42
-21
lines changed

bson/json_util.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@
4747
instances (as they are extended strings you can't provide custom defaults),
4848
but it will be faster as there is less recursion.
4949
50+
.. versionchanged:: 2.7
51+
Preserves order when rendering SON, Timestamp, Code, Binary, and DBRef
52+
instances. (But not in Python 2.4.)
53+
5054
.. versionchanged:: 2.3
5155
Added dumps and loads helpers to automatically handle conversion to and
5256
from json and supports :class:`~bson.binary.Binary` and
@@ -114,7 +118,7 @@ def dumps(obj, *args, **kwargs):
114118
115119
.. versionchanged:: 2.7
116120
Preserves order when rendering SON, Timestamp, Code, Binary, and DBRef
117-
instances.
121+
instances. (But not in Python 2.4.)
118122
"""
119123
if not json_lib:
120124
raise Exception("No json library available")
@@ -193,6 +197,14 @@ def object_hook(dct, compile_re=True):
193197

194198

195199
def default(obj):
200+
# We preserve key order when rendering SON, DBRef, etc. as JSON by
201+
# returning a SON for those types instead of a dict. This works with
202+
# the "json" standard library in Python 2.6+ and with simplejson
203+
# 2.1.0+ in Python 2.5+, because those libraries iterate the SON
204+
# using PyIter_Next. Python 2.4 must use simplejson 2.0.9 or older,
205+
# and those versions of simplejson use the lower-level PyDict_Next,
206+
# which bypasses SON's order-preserving iteration, so we lose key
207+
# order in Python 2.4.
196208
if isinstance(obj, ObjectId):
197209
return {"$oid": str(obj)}
198210
if isinstance(obj, DBRef):

test/test_json_util.py

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
from test.test_client import get_client
4141

4242
PY3 = sys.version_info[0] == 3
43+
PY24 = sys.version_info[:2] == (2, 4)
4344

4445

4546
class TestJsonUtil(unittest.TestCase):
@@ -70,10 +71,11 @@ def test_dbref(self):
7071
self.round_trip({"ref": DBRef("foo", 5, "db")})
7172
self.round_trip({"ref": DBRef("foo", ObjectId())})
7273

73-
# Check order.
74-
self.assertEqual(
75-
'{"$ref": "collection", "$id": 1, "$db": "db"}',
76-
json_util.dumps(DBRef('collection', 1, 'db')))
74+
if not PY24:
75+
# Check order.
76+
self.assertEqual(
77+
'{"$ref": "collection", "$id": 1, "$db": "db"}',
78+
json_util.dumps(DBRef('collection', 1, 'db')))
7779

7880
def test_datetime(self):
7981
# only millis, not micros
@@ -130,14 +132,15 @@ def test_regex(self):
130132
'{"r": {"$regex": ".*", "$options": "ilm"}}',
131133
compile_re=False)['r'])
132134

133-
# Check order.
134-
self.assertEqual(
135-
'{"$regex": ".*", "$options": "mx"}',
136-
json_util.dumps(Regex('.*', re.M | re.X)))
135+
if not PY24:
136+
# Check order.
137+
self.assertEqual(
138+
'{"$regex": ".*", "$options": "mx"}',
139+
json_util.dumps(Regex('.*', re.M | re.X)))
137140

138-
self.assertEqual(
139-
'{"$regex": ".*", "$options": "mx"}',
140-
json_util.dumps(re.compile(b('.*'), re.M | re.X)))
141+
self.assertEqual(
142+
'{"$regex": ".*", "$options": "mx"}',
143+
json_util.dumps(re.compile(b('.*'), re.M | re.X)))
141144

142145
def test_minkey(self):
143146
self.round_trip({"m": MinKey()})
@@ -146,10 +149,12 @@ def test_maxkey(self):
146149
self.round_trip({"m": MaxKey()})
147150

148151
def test_timestamp(self):
149-
res = json_util.json.dumps({"ts": Timestamp(4, 13)},
150-
default=json_util.default)
151-
self.assertEqual('{"ts": {"t": 4, "i": 13}}', res)
152-
dct = json_util.json.loads(res)
152+
res = json_util.dumps({"ts": Timestamp(4, 13)}, default=json_util.default)
153+
if not PY24:
154+
# Check order.
155+
self.assertEqual('{"ts": {"t": 4, "i": 13}}', res)
156+
157+
dct = json_util.loads(res)
153158
self.assertEqual(dct['ts']['t'], 4)
154159
self.assertEqual(dct['ts']['i'], 13)
155160

@@ -178,9 +183,12 @@ def test_binary(self):
178183
json_util.loads('{"bin": {"$type": 0, "$binary": "AAECAwQ="}}'))
179184

180185
json_bin_dump = json_util.dumps(md5_type_dict)
181-
self.assertEqual(
182-
'{"md5": {"$binary": "IG43GK8JL9HRL4DK53HMrA==", "$type": "05"}}',
183-
json_bin_dump)
186+
if not PY24:
187+
# Check order.
188+
self.assertEqual(
189+
'{"md5": {"$binary": "IG43GK8JL9HRL4DK53HMrA==",'
190+
+ ' "$type": "05"}}',
191+
json_bin_dump)
184192

185193
self.assertEqual(md5_type_dict,
186194
json_util.loads('{"md5": {"$type": 5, "$binary":'
@@ -208,8 +216,9 @@ def test_code(self):
208216
res = json_util.dumps(code)
209217
self.assertEqual(code, json_util.loads(res))
210218

211-
# Check order.
212-
self.assertEqual('{"$code": "return z", "$scope": {"z": 2}}', res)
219+
if not PY24:
220+
# Check order.
221+
self.assertEqual('{"$code": "return z", "$scope": {"z": 2}}', res)
213222

214223
def test_cursor(self):
215224
db = self.db

0 commit comments

Comments
 (0)