Skip to content

Commit e4d36ae

Browse files
author
Mike Dirolf
committed
add support for datetime and regex in json_util PYTHON-76
1 parent 1202e1b commit e4d36ae

File tree

2 files changed

+55
-8
lines changed

2 files changed

+55
-8
lines changed

pymongo/json_util.py

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,28 +30,63 @@
3030
3131
>>> json.loads(..., object_hook=json_util.object_hook)
3232
33-
Currently this only handles special encoding and decoding for
34-
:class:`~pymongo.objectid.ObjectId` and :class:`~pymongo.dbref.DBRef`
33+
Currently this does not handle special encoding and decoding for
34+
:class:`~pymongo.binary.Binary` and :class:`~pymongo.code.Code`
3535
instances.
36+
37+
.. versionchanged:: 1.1.2+
38+
Added support for encoding/decoding datetimes and regular expressions.
3639
"""
3740

41+
import datetime
42+
import calendar
43+
import re
44+
3845
from objectid import ObjectId
3946
from dbref import DBRef
4047

41-
# TODO support other types, like Binary, Code, datetime & regex
48+
# TODO support Binary and Code
4249
# Binary and Code are tricky because they subclass str so json thinks it can
4350
# handle them. Not sure what the proper way to get around this is...
51+
#
52+
# One option is to just add some other method that users need to call _before_
53+
# calling json.dumps or json.loads. That is pretty terrible though...
54+
55+
# TODO share this with bson.py?
56+
_RE_TYPE = type(re.compile("foo"))
4457

4558
def object_hook(dct):
4659
if "$oid" in dct:
4760
return ObjectId(str(dct["$oid"]))
4861
if "$ref" in dct:
4962
return DBRef(dct["$ref"], dct["$id"], dct.get("$db", None))
63+
if "$date" in dct:
64+
return datetime.datetime.utcfromtimestamp(float(dct["$date"]) / 1000.0)
65+
if "$regex" in dct:
66+
flags = 0
67+
if "i" in dct["$options"]:
68+
flags |= re.IGNORECASE
69+
if "m" in dct["$options"]:
70+
flags |= re.MULTILINE
71+
return re.compile(dct["$regex"], flags)
5072
return dct
5173

5274
def default(obj):
5375
if isinstance(obj, ObjectId):
5476
return {"$oid": str(obj)}
5577
if isinstance(obj, DBRef):
5678
return obj.as_doc()
79+
if isinstance(obj, datetime.datetime):
80+
# TODO share this code w/ bson.py?
81+
millis = int(calendar.timegm(obj.timetuple()) * 1000 +
82+
obj.microsecond / 1000)
83+
return {"$date": str(millis)}
84+
if isinstance(obj, _RE_TYPE):
85+
flags = ""
86+
if obj.flags & re.IGNORECASE:
87+
flags += "i"
88+
if obj.flags & re.MULTILINE:
89+
flags += "m"
90+
return {"$regex": obj.pattern,
91+
"$options": flags}
5792
raise TypeError("%r is not JSON serializable" % obj)

test/test_json_util.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
"""Test some utilities for working with JSON and PyMongo."""
1616

1717
import unittest
18+
import datetime
19+
import re
1820
import sys
1921
json_lib = True
2022
try:
@@ -39,12 +41,12 @@ def setUp(self):
3941
if not json_lib:
4042
raise SkipTest()
4143

42-
def round_trip(self, doc):
43-
def round_tripped(doc):
44-
return json.loads(json.dumps(doc, default=default),
45-
object_hook=object_hook)
44+
def round_tripped(self, doc):
45+
return json.loads(json.dumps(doc, default=default),
46+
object_hook=object_hook)
4647

47-
self.assertEqual(doc, round_tripped(doc))
48+
def round_trip(self, doc):
49+
self.assertEqual(doc, self.round_tripped(doc))
4850

4951
def test_basic(self):
5052
self.round_trip({"hello": "world"})
@@ -56,6 +58,16 @@ def test_dbref(self):
5658
self.round_trip({"ref": DBRef("foo", 5)})
5759
self.round_trip({"ref": DBRef("foo", 5, "db")})
5860

61+
def test_datetime(self):
62+
# only millis, not micros
63+
self.round_trip({"date": datetime.datetime(2009, 12, 9, 15,
64+
49, 45, 191000)})
65+
66+
def test_regex(self):
67+
res = self.round_tripped({"r": re.compile("a*b", re.IGNORECASE)})["r"]
68+
self.assertEqual("a*b", res.pattern)
69+
self.assertEqual(re.IGNORECASE, res.flags)
70+
5971

6072
if __name__ == "__main__":
6173
unittest.main()

0 commit comments

Comments
 (0)