diff --git a/letsencrypt/acme/jose/interfaces.py b/letsencrypt/acme/jose/interfaces.py index 446a5d2b0..285f51747 100644 --- a/letsencrypt/acme/jose/interfaces.py +++ b/letsencrypt/acme/jose/interfaces.py @@ -129,18 +129,24 @@ class JSONDeSerializable(object): :returns: Fully serialized object. """ - partial = self.to_json() - try_serialize = (lambda x: x.fully_serialize() - if isinstance(x, JSONDeSerializable) else x) - if isinstance(partial, basestring): # strings are sequences - return partial - if isinstance(partial, collections.Sequence): - return [try_serialize(elem) for elem in partial] - elif isinstance(partial, collections.Mapping): - return dict([(try_serialize(key), try_serialize(value)) - for key, value in partial.iteritems()]) - else: - return partial + def _serialize(obj): + if isinstance(obj, JSONDeSerializable): + return _serialize(obj.to_json()) + if isinstance(obj, basestring): # strings are sequence + return obj + elif isinstance(obj, list): + return [_serialize(subobj) for subobj in obj] + elif isinstance(obj, collections.Sequence): + # default to tuple, otherwise Mapping could get + # unhashable list + return tuple(_serialize(subobj) for subobj in obj) + elif isinstance(obj, collections.Mapping): + return dict((_serialize(key), _serialize(value)) + for key, value in obj.iteritems()) + else: + return obj + + return _serialize(self) @util.abstractclassmethod def from_json(cls, unused_jobj): diff --git a/letsencrypt/acme/jose/interfaces_test.py b/letsencrypt/acme/jose/interfaces_test.py index 2e5606bce..90e34d66d 100644 --- a/letsencrypt/acme/jose/interfaces_test.py +++ b/letsencrypt/acme/jose/interfaces_test.py @@ -3,6 +3,7 @@ import unittest class JSONDeSerializableTest(unittest.TestCase): + # pylint: disable=too-many-instance-attributes def setUp(self): from letsencrypt.acme.jose.interfaces import JSONDeSerializable @@ -50,6 +51,8 @@ class JSONDeSerializableTest(unittest.TestCase): self.basic2 = Basic('foo2') self.seq = Sequence(self.basic1, self.basic2) self.mapping = Mapping(self.basic1, self.basic2) + self.nested = Basic([[self.basic1]]) + self.tuple = Basic(('foo',)) # pylint: disable=invalid-name self.Basic = Basic @@ -66,6 +69,12 @@ class JSONDeSerializableTest(unittest.TestCase): mock_value = object() self.assertTrue(self.Basic(mock_value).fully_serialize() is mock_value) + def test_fully_serialize_nested(self): + self.assertEqual(self.nested.fully_serialize(), [['foo1']]) + + def test_fully_serialize(self): + self.assertEqual(self.tuple.fully_serialize(), (('foo', ))) + def test_from_json_not_implemented(self): from letsencrypt.acme.jose.interfaces import JSONDeSerializable self.assertRaises(TypeError, JSONDeSerializable.from_json, 'xxx')