1414
1515"""Tools for using Python's :mod:`json` module with BSON documents.
1616
17- This module provides two methods: `object_hook` and `default`. These
18- names are pretty terrible, but match the names used in Python's `json
19- library <http://docs.python.org/library/json.html>`_. They allow for
20- specialized encoding and decoding of BSON documents into `Mongo
21- Extended JSON
17+ This module provides two helper methods `dumps` and `loads` that wrap the
18+ native :mod:`json` methods and provide explicit BSON conversion to and from
19+ json. This allows for specialized encoding and decoding of BSON documents
20+ into `Mongo Extended JSON
2221<http://www.mongodb.org/display/DOCS/Mongo+Extended+JSON>`_'s *Strict*
2322mode. This lets you encode / decode BSON documents to JSON even when
2423they use special BSON types.
2524
2625Example usage (serialization)::
2726
28- >>> json.dumps(..., default=json_util.default)
27+ .. doctest::
28+
29+ >>> from bson import Binary, Code
30+ >>> from bson.json_util import dumps
31+ >>> dumps([{'foo': [1, 2]},
32+ ... {'bar': {'hello': 'world'}},
33+ ... {'code': Code("function x() { return 1; }")},
34+ ... {'bin': Binary("\x00 \x01 \x02 \x03 \x04 ")}])
35+ '[{"foo": [1, 2]}, {"bar": {"hello": "world"}}, {"code": {"$scope": {}, "$code": "function x() { return 1; }"}}, {"bin": {"$type": 0, "$binary": "AAECAwQ=\\ n"}}]'
2936
3037Example usage (deserialization)::
3138
32- >>> json.loads(..., object_hook=json_util.object_hook)
39+ .. doctest::
40+
41+ >>> from bson.json_util import loads
42+ >>> loads('[{"foo": [1, 2]}, {"bar": {"hello": "world"}}, {"code": {"$scope": {}, "$code": "function x() { return 1; }"}}, {"bin": {"$type": 0, "$binary": "AAECAwQ=\\ n"}}]')
43+ [{u'foo': [1, 2]}, {u'bar': {u'hello': u'world'}}, {u'code': Code('function x() { return 1; }', {})}, {u'bin': Binary('\x00 \x01 \x02 \x03 \x04 ', 0)}]
3344
34- Currently this does not handle special encoding and decoding for
35- :class:`~bson.binary.Binary` and :class:`~bson.code.Code` instances.
45+ Alternatively, you can manually pass the `default` to :func:`json.dumps`.
46+ It won't handle :class:`~bson.binary.Binary` and :class:`~bson.code.Code`
47+ instances (as they are extended strings you can't provide custom defaults),
48+ but it will be faster as there is less recursion.
49+
50+ .. versionchanged:: 2.2.1+
51+ Added dumps and loads helpers to automatically handle conversion to and
52+ from json and supports :class:`~bson.binary.Binary` and
53+ :class:`~bson.code.Code`
3654
3755.. versionchanged:: 1.9
3856 Handle :class:`uuid.UUID` instances, whenever possible.
5068 Added support for encoding/decoding datetimes and regular expressions.
5169"""
5270
71+ import base64
5372import calendar
5473import datetime
5574import re
75+
76+ json_lib = True
5677try :
57- import uuid
58- _use_uuid = True
78+ import json
5979except ImportError :
60- _use_uuid = False
80+ try :
81+ import simplejson as json
82+ except ImportError :
83+ json_lib = False
6184
85+ import bson
6286from bson import EPOCH_AWARE
87+ from bson .binary import Binary
88+ from bson .code import Code
6389from bson .dbref import DBRef
6490from bson .max_key import MaxKey
6591from bson .min_key import MinKey
6692from bson .objectid import ObjectId
6793from bson .timestamp import Timestamp
68- from bson .tz_util import utc
6994
70- # TODO support Binary and Code
71- # Binary and Code are tricky because they subclass str so json thinks it can
72- # handle them. Not sure what the proper way to get around this is...
73- #
74- # One option is to just add some other method that users need to call _before_
75- # calling json.dumps or json.loads. That is pretty terrible though...
95+ from bson .py3compat import PY3 , binary_type , string_types
7696
7797# TODO share this with bson.py?
7898_RE_TYPE = type (re .compile ("foo" ))
7999
80100
101+ def dumps (obj , * args , ** kwargs ):
102+ """Helper function that wraps :class:`json.dumps`.
103+
104+ Recursive function that handles all BSON types incuding
105+ :class:`~bson.binary.Binary` and :class:`~bson.code.Code`.
106+ """
107+ if not json_lib :
108+ raise Exception ("No json library available" )
109+ return json .dumps (_json_convert (obj ), * args , ** kwargs )
110+
111+
112+ def loads (s , * args , ** kwargs ):
113+ """Helper function that wraps :class:`json.loads`.
114+
115+ Automatically passes the object_hook for BSON type conversion.
116+ """
117+ if not json_lib :
118+ raise Exception ("No json library available" )
119+ kwargs ['object_hook' ] = object_hook
120+ return json .loads (s , * args , ** kwargs )
121+
122+
123+ def _json_convert (obj ):
124+ """Recursive helper method that converts BSON types so they can be
125+ converted into json.
126+ """
127+ if hasattr (obj , 'iteritems' ) or hasattr (obj , 'items' ): # PY3 support
128+ return dict (((k , _json_convert (v )) for k , v in obj .iteritems ()))
129+ elif hasattr (obj , '__iter__' ) and not isinstance (obj , string_types ):
130+ return list ((_json_convert (v ) for v in obj ))
131+ try :
132+ return default (obj )
133+ except TypeError :
134+ return obj
135+
136+
81137def object_hook (dct ):
82138 if "$oid" in dct :
83139 return ObjectId (str (dct ["$oid" ]))
@@ -97,8 +153,12 @@ def object_hook(dct):
97153 return MinKey ()
98154 if "$maxKey" in dct :
99155 return MaxKey ()
100- if _use_uuid and "$uuid" in dct :
101- return uuid .UUID (dct ["$uuid" ])
156+ if "$binary" in dct :
157+ return Binary (base64 .b64decode (dct ["$binary" ].encode ()), dct ["$type" ])
158+ if "$code" in dct :
159+ return Code (dct ["$code" ], dct .get ("$scope" ))
160+ if bson .has_uuid () and "$uuid" in dct :
161+ return bson .uuid .UUID (dct ["$uuid" ])
102162 return dct
103163
104164
@@ -128,6 +188,14 @@ def default(obj):
128188 return {"$maxKey" : 1 }
129189 if isinstance (obj , Timestamp ):
130190 return {"t" : obj .time , "i" : obj .inc }
131- if _use_uuid and isinstance (obj , uuid .UUID ):
191+ if isinstance (obj , Code ):
192+ return {'$code' : "%s" % obj , '$scope' : obj .scope }
193+ if isinstance (obj , Binary ):
194+ return {'$binary' : base64 .b64encode (obj ).decode (),
195+ '$type' : obj .subtype }
196+ if PY3 and isinstance (obj , binary_type ):
197+ return {'$binary' : base64 .b64encode (obj ).decode (),
198+ '$type' : 0 }
199+ if bson .has_uuid () and isinstance (obj , bson .uuid .UUID ):
132200 return {"$uuid" : obj .hex }
133201 raise TypeError ("%r is not JSON serializable" % obj )
0 commit comments