From 263281d71d0425d7bb9b4ebbdf1811ef637ee60a Mon Sep 17 00:00:00 2001 From: Malcolm Box Date: Thu, 21 Nov 2013 20:09:48 +0000 Subject: [PATCH 1/2] Fix issue #1231: JSONEncoder doesn't handle dict-like objects Check for __getitem__ and then attempt to convert to a dict. The check for __getitem__ is there as there's no universal way to check if an object is a mapping type, but this is a likely proxy --- rest_framework/tests/test_renderers.py | 57 ++++++++++++++++++++++++++ rest_framework/utils/encoders.py | 6 +++ 2 files changed, 63 insertions(+) diff --git a/rest_framework/tests/test_renderers.py b/rest_framework/tests/test_renderers.py index 76299a890..18da6ef85 100644 --- a/rest_framework/tests/test_renderers.py +++ b/rest_framework/tests/test_renderers.py @@ -18,6 +18,9 @@ from rest_framework.test import APIRequestFactory import datetime import pickle import re +import UserDict +import collections +import json DUMMYSTATUS = status.HTTP_200_OK @@ -244,6 +247,60 @@ class JSONRendererTests(TestCase): ret = JSONRenderer().render(_('test')) self.assertEqual(ret, b'"test"') + def test_render_userdict_obj(self): + class DictLike(UserDict.DictMixin): + def __init__(self): + self._dict = dict() + def __getitem__(self, key): + return self._dict.__getitem__(key) + def __setitem__(self, key, value): + return self._dict.__setitem__(key, value) + def __delitem__(self, key): + return self._dict.__delitem__(key) + def keys(self): + return self._dict.keys() + x = DictLike() + x['a'] = 1 + x['b'] = "string value" + ret = JSONRenderer().render(x) + self.assertEquals(json.loads(ret), {u'a': 1, u'b': u'string value'}) + + def test_render_dict_abc_obj(self): + class Dict(collections.MutableMapping): + def __init__(self): + self._dict = dict() + def __getitem__(self, key): + return self._dict.__getitem__(key) + def __setitem__(self, key, value): + return self._dict.__setitem__(key, value) + def __delitem__(self, key): + return self._dict.__delitem__(key) + def __iter__(self): + return self._dict.__iter__() + def __len__(self): + return self._dict.__len__() + + x = Dict() + x['key'] = 'string value' + x[2] = 3 + ret = JSONRenderer().render(x) + self.assertEquals(json.loads(ret), {u'key': 'string value', u'2': 3}) + + + def test_render_obj_with_getitem(self): + class DictLike(object): + def __init__(self): + self._dict = {} + def set(self, value): + self._dict = dict(value) + def __getitem__(self, key): + return self._dict[key] + + x = DictLike() + x.set({'a': 1, 'b': 'string'}) + with self.assertRaises(TypeError): + JSONRenderer().render(x) + def test_without_content_type_args(self): """ Test basic JSON rendering. diff --git a/rest_framework/utils/encoders.py b/rest_framework/utils/encoders.py index 35ad206b9..22b1ab3de 100644 --- a/rest_framework/utils/encoders.py +++ b/rest_framework/utils/encoders.py @@ -44,6 +44,12 @@ class JSONEncoder(json.JSONEncoder): return str(o) elif hasattr(o, 'tolist'): return o.tolist() + elif hasattr(o, '__getitem__'): + try: + return dict(o) + except KeyError: + # Couldn't convert to a dict, fall through + pass elif hasattr(o, '__iter__'): return [i for i in o] return super(JSONEncoder, self).default(o) From 6af31ed3945fd051a6e8c08851d7a656637d1f00 Mon Sep 17 00:00:00 2001 From: Malcolm Box Date: Fri, 22 Nov 2013 10:59:48 +0000 Subject: [PATCH 2/2] Remove u from literals --- rest_framework/tests/test_renderers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest_framework/tests/test_renderers.py b/rest_framework/tests/test_renderers.py index 18da6ef85..9c9c7452e 100644 --- a/rest_framework/tests/test_renderers.py +++ b/rest_framework/tests/test_renderers.py @@ -263,7 +263,7 @@ class JSONRendererTests(TestCase): x['a'] = 1 x['b'] = "string value" ret = JSONRenderer().render(x) - self.assertEquals(json.loads(ret), {u'a': 1, u'b': u'string value'}) + self.assertEquals(json.loads(ret), {'a': 1, 'b': 'string value'}) def test_render_dict_abc_obj(self): class Dict(collections.MutableMapping): @@ -284,7 +284,7 @@ class JSONRendererTests(TestCase): x['key'] = 'string value' x[2] = 3 ret = JSONRenderer().render(x) - self.assertEquals(json.loads(ret), {u'key': 'string value', u'2': 3}) + self.assertEquals(json.loads(ret), {'key': 'string value', '2': 3}) def test_render_obj_with_getitem(self):