django-tasty-pie превращает поле char в JSON

Я использую JSONfield для хранения некоторых данных в формате JSON в одном поле. Но когда я перехожу к своему API, построенному с помощью delicious-pie, он возвращает этого jason в виде строки, а не вложенного JSON, как я ожидал.

модели.py

class Item(models.Model):
    user = models.ForeignKey(User)
    body = JSONField(max_length=1024)

API.py

 class ItemResource(ModelResource):
    authorization = Authorization()
    authentication = SessionAuthentication()
    list_allowed_methods = ['get', 'post']

    class Meta:
        queryset = Item.objects.filter()

    def get_object_list(self, request):
        return super(ItemResource, self).get_object_list(request).filter(user=request.user)

    def apply_authorization_limits(self, request, object_list):
        return object_list.filter(user=request.user)

    def dehydrate(self, bundle):
        # how to modify bundle['body'] here ? so it work
        return bundle

Очевидно, что JSONfield основан на стандартном TextField django. Поэтому, когда я получаю API, я получаю следующее:

 {
    "meta": {
        "limit": 20,
        "next": null,
        "offset": 0,
        "previous": null,
        "total_count": 1
    },
    "objects": [
       {
        "body": "{u'valid': u'json'}",
        "id": 1,
        "resource_uri": "/api/v1/testitem/1/"
        }
    ]
  }

и это то, что я хочу получить:

 {
    "meta": {
        "limit": 20,
        "next": null,
        "offset": 0,
        "previous": null,
        "total_count": 1
    },
    "objects": [
       {
        "body": {
             "valid': "json"
           },
        "id": 1,
        "resource_uri": "/api/v1/testitem/1/"
        }
    ]
  }

Заметили разницу в поле body?

И если я сделаю:

    def dehydrate(self, bundle):
        bundle['body'] = json.loads(bundle['body'])
        return bundle

Я получаю это исключение:

{"error_message": "Expecting property name: line 1 column 1 (char 1)", "traceback": "Traceback (most recent call last):\n\n  File \"/Users/polinom/Envs/django/lib/python2.7/site-packages/tastypie/resources.py\", line 202, in wrapper\n    response = callback(request, *args, **kwargs)\n\n  File \"/Users/polinom/Envs/django/lib/python2.7/site-packages/tastypie/resources.py\", line 439, in dispatch_list\n    return self.dispatch('list', request, **kwargs)\n\n  File \"/Users/polinom/Envs/django/lib/python2.7/site-packages/tastypie/resources.py\", line 471, in dispatch\n    response = method(request, **kwargs)\n\n  File \"/Users/polinom/Envs/django/lib/python2.7/site-packages/tastypie/resources.py\", line 1270, in get_list\n    bundles.append(self.full_dehydrate(bundle))\n\n  File \"/Users/polinom/Envs/django/lib/python2.7/site-packages/tastypie/resources.py\", line 845, in full_dehydrate\n    bundle = self.dehydrate(bundle)\n\n  File \"/Users/polinom/workspace/microjob/applications/examinations/api.py\", line 24, in dehydrate\n    bundle.data['body'] = json.loads(bundle.data['body'])\n\n  File \"/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/__init__.py\", line 326, in loads\n    return _default_decoder.decode(s)\n\n  File \"/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/decoder.py\", line 360, in decode\n    obj, end = self.raw_decode(s, idx=_w(s, 0).end())\n\n  File \"/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/decoder.py\", line 376, in raw_decode\n    obj, end = self.scan_once(s, idx)\n\nValueError: Expecting property name: line 1 column 1 (char 1)\n"}

person Pol    schedule 13.03.2013    source источник


Ответы (1)


После создания пакетов создается ответ путем сериализации контента. Для формата json и сериализатора по умолчанию он сначала преобразует любой сложный тип в базовые типы Python, а затем выгружает его в строковое представление.

Как видите, в случае исходной строки она просто преобразуется в юникод. Вам нужно преобразовать его в dict перед сериализацией ресурса. Итак, в методе dehydrate вы можете сделать:

def dehydrate(self, bundle):
    bundle['body'] = json.loads(bundle.data['body'])
    return bundle

Поскольку вы изменяете только один элемент, вы также можете сделать:

def dehydrate_body(self, bundle):
    return json.loads(bundle.data['body'])

JSONField хранит представление python dict, а не представление JSON. Итак, моя первая попытка неверна. Вы можете return eval(bundle.data['body']). IMO использование eval здесь безопасно, потому что JSONField гарантирует, что содержимое является оператором, который может быть напрямую переведен в представление json.

person borges    schedule 13.03.2013
comment
И если я это сделаю, то получу взыскание. - person Pol; 14.03.2013
comment
спасибо, но мне нужно было сделать eval(bundle.data['body']), что небезопасно, верно? - person Pol; 14.03.2013
comment
@Pol: да, извини за это. Я не тестировал код. eval небезопасен в некоторых случаях. Но IMO безопасен, потому что JSONField обеспечивает допустимый json в содержимом. - person borges; 14.03.2013
comment
Спасибо! Я пока так уйду. Однако позже я напишу кастомное api.field для модели JSONfield. - person Pol; 14.03.2013
comment
вы можете использовать literal_eval вместо eval; согласно документам Python: см. ast.literal_eval() для функции, которая может безопасно оценивать строки с выражениями, содержащими только литералы. - person Jose A. Martín; 11.09.2013