PYTHON-2046 Change default JSONMode and dumps output from LEGACY to RELAXED (#711)
This commit is contained in:
parent
d9e5666336
commit
fb38fbe35e
@ -17,9 +17,9 @@
|
||||
This module provides two helper methods `dumps` and `loads` that wrap the
|
||||
native :mod:`json` methods and provide explicit BSON conversion to and from
|
||||
JSON. :class:`~bson.json_util.JSONOptions` provides a way to control how JSON
|
||||
is emitted and parsed, with the default being the legacy PyMongo format.
|
||||
:mod:`~bson.json_util` can also generate Canonical or Relaxed `Extended JSON`_
|
||||
when :const:`CANONICAL_JSON_OPTIONS` or :const:`RELAXED_JSON_OPTIONS` is
|
||||
is emitted and parsed, with the default being the Relaxed Extended JSON format.
|
||||
:mod:`~bson.json_util` can also generate Canonical or legacy `Extended JSON`_
|
||||
when :const:`CANONICAL_JSON_OPTIONS` or :const:`LEGACY_JSON_OPTIONS` is
|
||||
provided, respectively.
|
||||
|
||||
.. _Extended JSON: https://github.com/mongodb/specifications/blob/master/source/extended-json.rst
|
||||
@ -32,7 +32,7 @@ Example usage (deserialization):
|
||||
>>> loads('[{"foo": [1, 2]}, {"bar": {"hello": "world"}}, {"code": {"$scope": {}, "$code": "function x() { return 1; }"}}, {"bin": {"$type": "80", "$binary": "AQIDBA=="}}]')
|
||||
[{'foo': [1, 2]}, {'bar': {'hello': 'world'}}, {'code': Code('function x() { return 1; }', {})}, {'bin': Binary(b'...', 128)}]
|
||||
|
||||
Example usage (serialization):
|
||||
Example usage with :const:`RELAXED_JSON_OPTIONS` (the default):
|
||||
|
||||
.. doctest::
|
||||
|
||||
@ -40,9 +40,9 @@ Example usage (serialization):
|
||||
>>> from bson.json_util import dumps
|
||||
>>> dumps([{'foo': [1, 2]},
|
||||
... {'bar': {'hello': 'world'}},
|
||||
... {'code': Code("function x() { return 1; }", {})},
|
||||
... {'code': Code("function x() { return 1; }")},
|
||||
... {'bin': Binary(b"\x01\x02\x03\x04")}])
|
||||
'[{"foo": [1, 2]}, {"bar": {"hello": "world"}}, {"code": {"$code": "function x() { return 1; }", "$scope": {}}}, {"bin": {"$binary": "AQIDBA==", "$type": "00"}}]'
|
||||
'[{"foo": [1, 2]}, {"bar": {"hello": "world"}}, {"code": {"$code": "function x() { return 1; }"}}, {"bin": {"$binary": {"base64": "AQIDBA==", "subType": "00"}}}]'
|
||||
|
||||
Example usage (with :const:`CANONICAL_JSON_OPTIONS`):
|
||||
|
||||
@ -57,18 +57,18 @@ Example usage (with :const:`CANONICAL_JSON_OPTIONS`):
|
||||
... json_options=CANONICAL_JSON_OPTIONS)
|
||||
'[{"foo": [{"$numberInt": "1"}, {"$numberInt": "2"}]}, {"bar": {"hello": "world"}}, {"code": {"$code": "function x() { return 1; }"}}, {"bin": {"$binary": {"base64": "AQIDBA==", "subType": "00"}}}]'
|
||||
|
||||
Example usage (with :const:`RELAXED_JSON_OPTIONS`):
|
||||
Example usage (with :const:`LEGACY_JSON_OPTIONS`):
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> from bson import Binary, Code
|
||||
>>> from bson.json_util import dumps, RELAXED_JSON_OPTIONS
|
||||
>>> from bson.json_util import dumps, LEGACY_JSON_OPTIONS
|
||||
>>> dumps([{'foo': [1, 2]},
|
||||
... {'bar': {'hello': 'world'}},
|
||||
... {'code': Code("function x() { return 1; }")},
|
||||
... {'code': Code("function x() { return 1; }", {})},
|
||||
... {'bin': Binary(b"\x01\x02\x03\x04")}],
|
||||
... json_options=RELAXED_JSON_OPTIONS)
|
||||
'[{"foo": [1, 2]}, {"bar": {"hello": "world"}}, {"code": {"$code": "function x() { return 1; }"}}, {"bin": {"$binary": {"base64": "AQIDBA==", "subType": "00"}}}]'
|
||||
... json_options=LEGACY_JSON_OPTIONS)
|
||||
'[{"foo": [1, 2]}, {"bar": {"hello": "world"}}, {"code": {"$code": "function x() { return 1; }", "$scope": {}}}, {"bin": {"$binary": "AQIDBA==", "$type": "00"}}]'
|
||||
|
||||
Alternatively, you can manually pass the `default` to :func:`json.dumps`.
|
||||
It won't handle :class:`~bson.binary.Binary` and :class:`~bson.code.Code`
|
||||
@ -238,23 +238,27 @@ class JSONOptions(CodecOptions):
|
||||
|
||||
.. seealso:: The specification for Relaxed and Canonical `Extended JSON`_.
|
||||
|
||||
.. versionadded:: 3.4
|
||||
.. versionchanged:: 4.0
|
||||
The default for `json_mode` was changed from :const:`JSONMode.LEGACY`
|
||||
to :const:`JSONMode.RELAXED`.
|
||||
|
||||
.. versionchanged:: 3.5
|
||||
Accepts the optional parameter `json_mode`.
|
||||
|
||||
.. versionadded:: 3.4
|
||||
"""
|
||||
|
||||
def __new__(cls, strict_number_long=False,
|
||||
datetime_representation=DatetimeRepresentation.LEGACY,
|
||||
strict_uuid=False, json_mode=JSONMode.LEGACY,
|
||||
def __new__(cls, strict_number_long=None,
|
||||
datetime_representation=None,
|
||||
strict_uuid=None, json_mode=JSONMode.RELAXED,
|
||||
*args, **kwargs):
|
||||
kwargs["tz_aware"] = kwargs.get("tz_aware", True)
|
||||
if kwargs["tz_aware"]:
|
||||
kwargs["tzinfo"] = kwargs.get("tzinfo", utc)
|
||||
if datetime_representation not in (DatetimeRepresentation.LEGACY,
|
||||
DatetimeRepresentation.NUMBERLONG,
|
||||
DatetimeRepresentation.ISO8601):
|
||||
DatetimeRepresentation.ISO8601,
|
||||
None):
|
||||
raise ConfigurationError(
|
||||
"JSONOptions.datetime_representation must be one of LEGACY, "
|
||||
"NUMBERLONG, or ISO8601 from DatetimeRepresentation.")
|
||||
@ -267,17 +271,47 @@ class JSONOptions(CodecOptions):
|
||||
"or CANONICAL from JSONMode.")
|
||||
self.json_mode = json_mode
|
||||
if self.json_mode == JSONMode.RELAXED:
|
||||
if strict_number_long:
|
||||
raise ConfigurationError(
|
||||
"Cannot specify strict_number_long=True with"
|
||||
" JSONMode.RELAXED")
|
||||
if datetime_representation not in (None,
|
||||
DatetimeRepresentation.ISO8601):
|
||||
raise ConfigurationError(
|
||||
"datetime_representation must be DatetimeRepresentation."
|
||||
"ISO8601 or omitted with JSONMode.RELAXED")
|
||||
if strict_uuid not in (None, True):
|
||||
raise ConfigurationError(
|
||||
"Cannot specify strict_uuid=False with JSONMode.RELAXED")
|
||||
self.strict_number_long = False
|
||||
self.datetime_representation = DatetimeRepresentation.ISO8601
|
||||
self.strict_uuid = True
|
||||
elif self.json_mode == JSONMode.CANONICAL:
|
||||
if strict_number_long not in (None, True):
|
||||
raise ConfigurationError(
|
||||
"Cannot specify strict_number_long=False with"
|
||||
" JSONMode.RELAXED")
|
||||
if datetime_representation not in (
|
||||
None, DatetimeRepresentation.NUMBERLONG):
|
||||
raise ConfigurationError(
|
||||
"datetime_representation must be DatetimeRepresentation."
|
||||
"NUMBERLONG or omitted with JSONMode.RELAXED")
|
||||
if strict_uuid not in (None, True):
|
||||
raise ConfigurationError(
|
||||
"Cannot specify strict_uuid=False with JSONMode.RELAXED")
|
||||
self.strict_number_long = True
|
||||
self.datetime_representation = DatetimeRepresentation.NUMBERLONG
|
||||
self.strict_uuid = True
|
||||
else:
|
||||
self.strict_number_long = strict_number_long
|
||||
self.datetime_representation = datetime_representation
|
||||
self.strict_uuid = strict_uuid
|
||||
else: # JSONMode.LEGACY
|
||||
self.strict_number_long = False
|
||||
self.datetime_representation = DatetimeRepresentation.LEGACY
|
||||
self.strict_uuid = False
|
||||
if strict_number_long is not None:
|
||||
self.strict_number_long = strict_number_long
|
||||
if datetime_representation is not None:
|
||||
self.datetime_representation = datetime_representation
|
||||
if strict_uuid is not None:
|
||||
self.strict_uuid = strict_uuid
|
||||
return self
|
||||
|
||||
def _arguments_repr(self):
|
||||
@ -307,7 +341,7 @@ class JSONOptions(CodecOptions):
|
||||
>>> from bson.json_util import CANONICAL_JSON_OPTIONS
|
||||
>>> CANONICAL_JSON_OPTIONS.tz_aware
|
||||
True
|
||||
>>> json_options = CANONICAL_JSON_OPTIONS.with_options(tz_aware=False)
|
||||
>>> json_options = CANONICAL_JSON_OPTIONS.with_options(tz_aware=False, tzinfo=None)
|
||||
>>> json_options.tz_aware
|
||||
False
|
||||
|
||||
@ -329,15 +363,6 @@ LEGACY_JSON_OPTIONS = JSONOptions(json_mode=JSONMode.LEGACY)
|
||||
.. versionadded:: 3.5
|
||||
"""
|
||||
|
||||
DEFAULT_JSON_OPTIONS = LEGACY_JSON_OPTIONS
|
||||
"""The default :class:`JSONOptions` for JSON encoding/decoding.
|
||||
|
||||
The same as :const:`LEGACY_JSON_OPTIONS`. This will change to
|
||||
:const:`RELAXED_JSON_OPTIONS` in a future release.
|
||||
|
||||
.. versionadded:: 3.4
|
||||
"""
|
||||
|
||||
CANONICAL_JSON_OPTIONS = JSONOptions(json_mode=JSONMode.CANONICAL)
|
||||
""":class:`JSONOptions` for Canonical Extended JSON.
|
||||
|
||||
@ -354,18 +379,16 @@ RELAXED_JSON_OPTIONS = JSONOptions(json_mode=JSONMode.RELAXED)
|
||||
.. versionadded:: 3.5
|
||||
"""
|
||||
|
||||
STRICT_JSON_OPTIONS = JSONOptions(
|
||||
strict_number_long=True,
|
||||
datetime_representation=DatetimeRepresentation.ISO8601,
|
||||
strict_uuid=True)
|
||||
"""**DEPRECATED** - :class:`JSONOptions` for MongoDB Extended JSON's *Strict
|
||||
mode* encoding.
|
||||
DEFAULT_JSON_OPTIONS = RELAXED_JSON_OPTIONS
|
||||
"""The default :class:`JSONOptions` for JSON encoding/decoding.
|
||||
|
||||
The same as :const:`RELAXED_JSON_OPTIONS`.
|
||||
|
||||
.. versionchanged:: 4.0
|
||||
Changed from :const:`LEGACY_JSON_OPTIONS` to
|
||||
:const:`RELAXED_JSON_OPTIONS`.
|
||||
|
||||
.. versionadded:: 3.4
|
||||
|
||||
.. versionchanged:: 3.5
|
||||
Deprecated. Use :const:`RELAXED_JSON_OPTIONS` or
|
||||
:const:`CANONICAL_JSON_OPTIONS` instead.
|
||||
"""
|
||||
|
||||
|
||||
@ -380,6 +403,10 @@ def dumps(obj, *args, **kwargs):
|
||||
encoding of MongoDB Extended JSON types. Defaults to
|
||||
:const:`DEFAULT_JSON_OPTIONS`.
|
||||
|
||||
.. versionchanged:: 4.0
|
||||
Now outputs MongoDB Relaxed Extended JSON by default (using
|
||||
:const:`DEFAULT_JSON_OPTIONS`).
|
||||
|
||||
.. versionchanged:: 3.4
|
||||
Accepts optional parameter `json_options`. See :class:`JSONOptions`.
|
||||
"""
|
||||
|
||||
@ -98,6 +98,12 @@ Breaking Changes in 4.0
|
||||
- Removed :exc:`pymongo.errors.CertificateError`.
|
||||
- Removed :attr:`pymongo.GEOHAYSTACK`.
|
||||
- Removed :class:`bson.binary.UUIDLegacy`.
|
||||
- Removed :const:`bson.json_util.STRICT_JSON_OPTIONS`. Use
|
||||
:const:`~bson.json_util.RELAXED_JSON_OPTIONS` or
|
||||
:const:`~bson.json_util.CANONICAL_JSON_OPTIONS` instead.
|
||||
- Changed the default JSON encoding representation from legacy to relaxed.
|
||||
The json_mode parameter for :const:`bson.json_util.dumps` now defaults to
|
||||
:const:`~bson.json_util.RELAXED_JSON_OPTIONS`.
|
||||
- The "tls" install extra is no longer necessary or supported and will be
|
||||
ignored by pip.
|
||||
- The ``hint`` option is now required when using ``min`` or ``max`` queries
|
||||
|
||||
@ -124,9 +124,7 @@ modifier. Code like this::
|
||||
# Set a 5 second select() timeout.
|
||||
>>> cursor = collection.find({"a": 1}, network_timeout=5)
|
||||
|
||||
can be changed to this with PyMongo 2.9 or later:
|
||||
|
||||
.. doctest::
|
||||
can be changed to this with PyMongo 2.9 or later::
|
||||
|
||||
# Set a 5 second (5000 millisecond) server side query timeout.
|
||||
>>> cursor = collection.find({"a": 1}, modifiers={"$maxTimeMS": 5000})
|
||||
|
||||
@ -675,6 +675,13 @@ can be changed to this::
|
||||
uu = uuid.uuid4()
|
||||
uuid_legacy = Binary.from_uuid(uu, PYTHON_LEGACY)
|
||||
|
||||
Default JSONMode changed from LEGACY to RELAXED
|
||||
-----------------------------------------------
|
||||
|
||||
Changed the default JSON encoding representation from legacy to relaxed.
|
||||
The json_mode parameter for :const:`bson.json_util.dumps` now defaults to
|
||||
:const:`~bson.json_util.RELAXED_JSON_OPTIONS`.
|
||||
|
||||
Removed features with no migration path
|
||||
---------------------------------------
|
||||
|
||||
|
||||
@ -658,7 +658,7 @@ class GridOut(io.IOBase):
|
||||
def __iter__(self):
|
||||
"""Return an iterator over all of this file's data.
|
||||
|
||||
The iterator will return lines (delimited by b'\n') of
|
||||
The iterator will return lines (delimited by ``b'\\n'``) of
|
||||
:class:`bytes`. This can be useful when serving files
|
||||
using a webserver that handles such an iterator efficiently.
|
||||
|
||||
|
||||
@ -71,7 +71,8 @@ codec_options_uuid_04 = codec_options._replace(uuid_representation=STANDARD)
|
||||
json_options_uuid_04 = json_util.JSONOptions(json_mode=JSONMode.CANONICAL,
|
||||
uuid_representation=STANDARD)
|
||||
json_options_iso8601 = json_util.JSONOptions(
|
||||
datetime_representation=json_util.DatetimeRepresentation.ISO8601)
|
||||
datetime_representation=json_util.DatetimeRepresentation.ISO8601,
|
||||
json_mode=JSONMode.LEGACY)
|
||||
to_extjson = functools.partial(json_util.dumps,
|
||||
json_options=json_util.CANONICAL_JSON_OPTIONS)
|
||||
to_extjson_uuid_04 = functools.partial(json_util.dumps,
|
||||
|
||||
@ -24,7 +24,9 @@ sys.path[0:0] = [""]
|
||||
|
||||
from bson import json_util, EPOCH_AWARE, SON
|
||||
from bson.json_util import (DatetimeRepresentation,
|
||||
STRICT_JSON_OPTIONS)
|
||||
JSONMode,
|
||||
JSONOptions,
|
||||
LEGACY_JSON_OPTIONS)
|
||||
from bson.binary import (ALL_UUID_REPRESENTATIONS, Binary, MD5_SUBTYPE,
|
||||
USER_DEFINED_SUBTYPE, UuidRepresentation, STANDARD)
|
||||
from bson.code import Code
|
||||
@ -40,6 +42,13 @@ from bson.tz_util import FixedOffset, utc
|
||||
from test import unittest, IntegrationTest
|
||||
|
||||
|
||||
STRICT_JSON_OPTIONS = JSONOptions(
|
||||
strict_number_long=True,
|
||||
datetime_representation=DatetimeRepresentation.ISO8601,
|
||||
strict_uuid=True,
|
||||
json_mode=JSONMode.LEGACY)
|
||||
|
||||
|
||||
class TestJsonUtil(unittest.TestCase):
|
||||
def round_tripped(self, doc, **kwargs):
|
||||
return json_util.loads(json_util.dumps(doc, **kwargs), **kwargs)
|
||||
@ -51,16 +60,18 @@ class TestJsonUtil(unittest.TestCase):
|
||||
self.round_trip({"hello": "world"})
|
||||
|
||||
def test_json_options_with_options(self):
|
||||
opts = json_util.JSONOptions(
|
||||
datetime_representation=DatetimeRepresentation.NUMBERLONG)
|
||||
opts = JSONOptions(
|
||||
datetime_representation=DatetimeRepresentation.NUMBERLONG,
|
||||
json_mode=JSONMode.LEGACY)
|
||||
self.assertEqual(
|
||||
opts.datetime_representation, DatetimeRepresentation.NUMBERLONG)
|
||||
opts2 = opts.with_options(
|
||||
datetime_representation=DatetimeRepresentation.ISO8601)
|
||||
datetime_representation=DatetimeRepresentation.ISO8601,
|
||||
json_mode=JSONMode.LEGACY)
|
||||
self.assertEqual(
|
||||
opts2.datetime_representation, DatetimeRepresentation.ISO8601)
|
||||
|
||||
opts = json_util.JSONOptions(strict_number_long=True)
|
||||
opts = JSONOptions(strict_number_long=True, json_mode=JSONMode.LEGACY)
|
||||
self.assertEqual(opts.strict_number_long, True)
|
||||
opts2 = opts.with_options(strict_number_long=False)
|
||||
self.assertEqual(opts2.strict_number_long, False)
|
||||
@ -152,11 +163,17 @@ class TestJsonUtil(unittest.TestCase):
|
||||
pre_epoch = {"dt": datetime.datetime(1, 1, 1, 1, 1, 1, 10000, utc)}
|
||||
post_epoch = {"dt": datetime.datetime(1972, 1, 1, 1, 1, 1, 10000, utc)}
|
||||
self.assertEqual(
|
||||
'{"dt": {"$date": -62135593138990}}',
|
||||
'{"dt": {"$date": {"$numberLong": "-62135593138990"}}}',
|
||||
json_util.dumps(pre_epoch))
|
||||
self.assertEqual(
|
||||
'{"dt": {"$date": 63075661010}}',
|
||||
'{"dt": {"$date": "1972-01-01T01:01:01.010Z"}}',
|
||||
json_util.dumps(post_epoch))
|
||||
self.assertEqual(
|
||||
'{"dt": {"$date": -62135593138990}}',
|
||||
json_util.dumps(pre_epoch, json_options=LEGACY_JSON_OPTIONS))
|
||||
self.assertEqual(
|
||||
'{"dt": {"$date": 63075661010}}',
|
||||
json_util.dumps(post_epoch, json_options=LEGACY_JSON_OPTIONS))
|
||||
self.assertEqual(
|
||||
'{"dt": {"$date": {"$numberLong": "-62135593138990"}}}',
|
||||
json_util.dumps(pre_epoch, json_options=STRICT_JSON_OPTIONS))
|
||||
@ -164,8 +181,9 @@ class TestJsonUtil(unittest.TestCase):
|
||||
'{"dt": {"$date": "1972-01-01T01:01:01.010Z"}}',
|
||||
json_util.dumps(post_epoch, json_options=STRICT_JSON_OPTIONS))
|
||||
|
||||
number_long_options = json_util.JSONOptions(
|
||||
datetime_representation=DatetimeRepresentation.NUMBERLONG)
|
||||
number_long_options = JSONOptions(
|
||||
datetime_representation=DatetimeRepresentation.NUMBERLONG,
|
||||
json_mode=JSONMode.LEGACY)
|
||||
self.assertEqual(
|
||||
'{"dt": {"$date": {"$numberLong": "63075661010"}}}',
|
||||
json_util.dumps(post_epoch, json_options=number_long_options))
|
||||
@ -194,14 +212,14 @@ class TestJsonUtil(unittest.TestCase):
|
||||
datetime.datetime(1972, 1, 1, 1, 1, 1, 10000, utc),
|
||||
json_util.loads(
|
||||
'{"dt": {"$date": "1972-01-01T01:01:01.010+0000"}}',
|
||||
json_options=json_util.JSONOptions(tz_aware=True,
|
||||
json_options=JSONOptions(tz_aware=True,
|
||||
tzinfo=utc))["dt"])
|
||||
self.assertEqual(
|
||||
datetime.datetime(1972, 1, 1, 1, 1, 1, 10000),
|
||||
json_util.loads(
|
||||
'{"dt": {"$date": "1972-01-01T01:01:01.010+0000"}}',
|
||||
json_options=json_util.JSONOptions(tz_aware=False))["dt"])
|
||||
self.round_trip(pre_epoch_naive, json_options=json_util.JSONOptions(
|
||||
json_options=JSONOptions(tz_aware=False))["dt"])
|
||||
self.round_trip(pre_epoch_naive, json_options=JSONOptions(
|
||||
tz_aware=False))
|
||||
|
||||
# Test a non-utc timezone
|
||||
@ -211,10 +229,12 @@ class TestJsonUtil(unittest.TestCase):
|
||||
self.assertEqual(
|
||||
'{"dt": {"$date": "2002-10-27T06:00:00.010-0800"}}',
|
||||
json_util.dumps(aware_datetime, json_options=STRICT_JSON_OPTIONS))
|
||||
self.round_trip(aware_datetime, json_options=json_util.JSONOptions(
|
||||
self.round_trip(aware_datetime, json_options=JSONOptions(
|
||||
json_mode=JSONMode.LEGACY,
|
||||
tz_aware=True, tzinfo=pacific))
|
||||
self.round_trip(aware_datetime, json_options=json_util.JSONOptions(
|
||||
self.round_trip(aware_datetime, json_options=JSONOptions(
|
||||
datetime_representation=DatetimeRepresentation.ISO8601,
|
||||
json_mode=JSONMode.LEGACY,
|
||||
tz_aware=True, tzinfo=pacific))
|
||||
|
||||
def test_regex_object_hook(self):
|
||||
@ -253,13 +273,18 @@ class TestJsonUtil(unittest.TestCase):
|
||||
|
||||
# Check order.
|
||||
self.assertEqual(
|
||||
'{"$regex": ".*", "$options": "mx"}',
|
||||
'{"$regularExpression": {"pattern": ".*", "options": "mx"}}',
|
||||
json_util.dumps(Regex('.*', re.M | re.X)))
|
||||
|
||||
self.assertEqual(
|
||||
'{"$regex": ".*", "$options": "mx"}',
|
||||
'{"$regularExpression": {"pattern": ".*", "options": "mx"}}',
|
||||
json_util.dumps(re.compile(b'.*', re.M | re.X)))
|
||||
|
||||
self.assertEqual(
|
||||
'{"$regex": ".*", "$options": "mx"}',
|
||||
json_util.dumps(Regex('.*', re.M | re.X),
|
||||
json_options=LEGACY_JSON_OPTIONS))
|
||||
|
||||
def test_minkey(self):
|
||||
self.round_trip({"m": MinKey()})
|
||||
|
||||
@ -278,26 +303,28 @@ class TestJsonUtil(unittest.TestCase):
|
||||
self.round_trip(doc)
|
||||
self.assertEqual(
|
||||
'{"uuid": {"$uuid": "f47ac10b58cc4372a5670e02b2c3d479"}}',
|
||||
json_util.dumps(doc))
|
||||
json_util.dumps(doc, json_options=LEGACY_JSON_OPTIONS))
|
||||
self.assertEqual(
|
||||
'{"uuid": '
|
||||
'{"$binary": "9HrBC1jMQ3KlZw4CssPUeQ==", "$type": "03"}}',
|
||||
json_util.dumps(
|
||||
doc, json_options=json_util.STRICT_JSON_OPTIONS))
|
||||
doc, json_options=STRICT_JSON_OPTIONS))
|
||||
self.assertEqual(
|
||||
'{"uuid": '
|
||||
'{"$binary": "9HrBC1jMQ3KlZw4CssPUeQ==", "$type": "04"}}',
|
||||
json_util.dumps(
|
||||
doc, json_options=json_util.JSONOptions(
|
||||
strict_uuid=True, uuid_representation=STANDARD)))
|
||||
doc, json_options=JSONOptions(
|
||||
strict_uuid=True, json_mode=JSONMode.LEGACY,
|
||||
uuid_representation=STANDARD)))
|
||||
self.assertEqual(
|
||||
doc, json_util.loads(
|
||||
'{"uuid": '
|
||||
'{"$binary": "9HrBC1jMQ3KlZw4CssPUeQ==", "$type": "03"}}'))
|
||||
for uuid_representation in (set(ALL_UUID_REPRESENTATIONS) -
|
||||
{UuidRepresentation.UNSPECIFIED}):
|
||||
options = json_util.JSONOptions(
|
||||
strict_uuid=True, uuid_representation=uuid_representation)
|
||||
options = JSONOptions(
|
||||
strict_uuid=True, json_mode=JSONMode.LEGACY,
|
||||
uuid_representation=uuid_representation)
|
||||
self.round_trip(doc, json_options=options)
|
||||
# Ignore UUID representation when decoding BSON binary subtype 4.
|
||||
self.assertEqual(doc, json_util.loads(
|
||||
@ -307,8 +334,9 @@ class TestJsonUtil(unittest.TestCase):
|
||||
|
||||
def test_uuid_uuid_rep_unspecified(self):
|
||||
_uuid = uuid.uuid4()
|
||||
options = json_util.JSONOptions(
|
||||
options = JSONOptions(
|
||||
strict_uuid=True,
|
||||
json_mode=JSONMode.LEGACY,
|
||||
uuid_representation=UuidRepresentation.UNSPECIFIED)
|
||||
|
||||
# Cannot directly encode native UUIDs with UNSPECIFIED.
|
||||
@ -329,7 +357,8 @@ class TestJsonUtil(unittest.TestCase):
|
||||
doc, json_util.loads(ext_json_str, json_options=options))
|
||||
# $uuid-encoded fields
|
||||
doc = {'uuid': Binary(_uuid.bytes, subtype=4)}
|
||||
ext_json_str = json_util.dumps({'uuid': _uuid})
|
||||
ext_json_str = json_util.dumps({'uuid': _uuid},
|
||||
json_options=LEGACY_JSON_OPTIONS)
|
||||
self.assertEqual(
|
||||
doc, json_util.loads(ext_json_str, json_options=options))
|
||||
|
||||
@ -350,11 +379,13 @@ class TestJsonUtil(unittest.TestCase):
|
||||
self.assertEqual(type(bin), bytes)
|
||||
|
||||
# PYTHON-443 ensure old type formats are supported
|
||||
json_bin_dump = json_util.dumps(bin_type_dict)
|
||||
self.assertTrue('"$type": "00"' in json_bin_dump)
|
||||
json_bin_dump = json_util.dumps(bin_type_dict,
|
||||
json_options=LEGACY_JSON_OPTIONS)
|
||||
self.assertIn('"$type": "00"', json_bin_dump)
|
||||
self.assertEqual(bin_type_dict,
|
||||
json_util.loads('{"bin": {"$type": 0, "$binary": "AAECAwQ="}}'))
|
||||
json_bin_dump = json_util.dumps(md5_type_dict)
|
||||
json_bin_dump = json_util.dumps(md5_type_dict,
|
||||
json_options=LEGACY_JSON_OPTIONS)
|
||||
# Check order.
|
||||
self.assertEqual(
|
||||
'{"md5": {"$binary": "IG43GK8JL9HRL4DK53HMrA==",'
|
||||
@ -365,8 +396,9 @@ class TestJsonUtil(unittest.TestCase):
|
||||
json_util.loads('{"md5": {"$type": 5, "$binary":'
|
||||
' "IG43GK8JL9HRL4DK53HMrA=="}}'))
|
||||
|
||||
json_bin_dump = json_util.dumps(custom_type_dict)
|
||||
self.assertTrue('"$type": "80"' in json_bin_dump)
|
||||
json_bin_dump = json_util.dumps(custom_type_dict,
|
||||
json_options=LEGACY_JSON_OPTIONS)
|
||||
self.assertIn('"$type": "80"', json_bin_dump)
|
||||
self.assertEqual(custom_type_dict,
|
||||
json_util.loads('{"custom": {"$type": 128, "$binary":'
|
||||
' "aGVsbG8="}}'))
|
||||
@ -404,7 +436,8 @@ class TestJsonUtil(unittest.TestCase):
|
||||
Int64(65535))
|
||||
self.assertEqual(json_util.dumps({"weight": Int64(65535)}),
|
||||
'{"weight": 65535}')
|
||||
json_options = json_util.JSONOptions(strict_number_long=True)
|
||||
json_options = JSONOptions(strict_number_long=True,
|
||||
json_mode=JSONMode.LEGACY)
|
||||
self.assertEqual(json_util.dumps({"weight": Int64(65535)},
|
||||
json_options=json_options),
|
||||
jsn)
|
||||
@ -413,10 +446,10 @@ class TestJsonUtil(unittest.TestCase):
|
||||
# document_class dict should always work
|
||||
self.assertEqual({"foo": "bar"}, json_util.loads(
|
||||
'{"foo": "bar"}',
|
||||
json_options=json_util.JSONOptions(document_class=dict)))
|
||||
json_options=JSONOptions(document_class=dict)))
|
||||
self.assertEqual(SON([("foo", "bar"), ("b", 1)]), json_util.loads(
|
||||
'{"foo": "bar", "b": 1}',
|
||||
json_options=json_util.JSONOptions(document_class=SON)))
|
||||
json_options=JSONOptions(document_class=SON)))
|
||||
|
||||
|
||||
class TestJsonUtilRoundtrip(IntegrationTest):
|
||||
|
||||
Loading…
Reference in New Issue
Block a user