22

Django 1.7 introduced the JsonResponse objects, which I try to use to return a list of values to my ajax request.

I want to pass

>>> Genre.objects.values('name', 'color')
[{'color': '8a3700', 'name': 'rock'}, {'color': 'ffff00', 'name': 'pop'}, {'color': '8f8f00', 'name': 'electronic'}, {'color': '9e009e', 'name': 'chillout'}, {'color': 'ff8838', 'name': 'indie'}, {'color': '0aff0a', 'name': 'techno'}, {'color': 'c20000', 'name': "drum'n'bass"}, {'color': '0000d6', 'name': 'worldmusic'}, {'color': 'a800a8', 'name': 'classic'}, {'color': 'dbdb00', 'name': 'hiphop'}]

to a JsonResponse object.

However, my attempts fail.

>>> JsonResponse({'foo': 'bar', 'blib': 'blab'}) # works
<django.http.response.JsonResponse object at 0x7f53d28bbb00>

>>> JsonResponse(Genre.objects.values('name', 'color')) # doesn't work
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/home/marcel/Dokumente/django/FlushFM/env/lib/python3.4/site-packages/django/http/response.py", line 476, in __init__
    raise TypeError('In order to allow non-dict objects to be '
TypeError: In order to allow non-dict objects to be serialized set the safe parameter to False

This is probably due to the different data structure of Genre.objects.values().

How would this be done right?

[edit]

With safe=False I get

>>> JsonResponse(Genre.objects.values('name', 'color'), safe=False)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/home/marcel/Dokumente/django/FlushFM/env/lib/python3.4/site-packages/django/http/response.py", line 479, in __init__
    data = json.dumps(data, cls=encoder)
  File "/usr/lib/python3.4/json/__init__.py", line 237, in dumps
    **kw).encode(obj)
  File "/usr/lib/python3.4/json/encoder.py", line 192, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/usr/lib/python3.4/json/encoder.py", line 250, in iterencode
    return _iterencode(o, 0)
  File "/home/marcel/Dokumente/django/FlushFM/env/lib/python3.4/site-packages/django/core/serializers/json.py", line 109, in default
    return super(DjangoJSONEncoder, self).default(o)
  File "/usr/lib/python3.4/json/encoder.py", line 173, in default
    raise TypeError(repr(o) + " is not JSON serializable")
TypeError: [{'color': '8a3700', 'name': 'rock'}, {'color': 'ffff00', 'name': 'pop'}, {'color': '8f8f00', 'name': 'electronic'}, {'color': '9e009e', 'name': 'chillout'}, {'color': 'ff8838', 'name': 'indie'}, {'color': '0aff0a', 'name': 'techno'}, {'color': 'c20000', 'name': "drum'n'bass"}, {'color': '0000d6', 'name': 'worldmusic'}, {'color': 'a800a8', 'name': 'classic'}, {'color': 'dbdb00', 'name': 'hiphop'}] is not JSON serializable

What works is

>>> JsonResponse(list(Genre.objects.values('name', 'color')), safe=False)
<django.http.response.JsonResponse object at 0x7f53d28bb9e8>

But isn't there a better way to generate a dict out of a Model object?

speendo
  • 13,045
  • 22
  • 71
  • 107
  • 1
    Did you try doing what the error message says? – Daniel Roseman Sep 26 '14 at 19:59
  • @DanielRoseman well yes, but also setting `safe=False` would result in an error message. Do you need the output? – speendo Sep 26 '14 at 20:02
  • 1
    @speendo Is it the same error with `safe=False` as well? Perhaps try it after passing the `ValuesQuerySet` to `list()`: `JsonResponse(list(Genre.objects.values('name', 'color')))` – Ashwini Chaudhary Sep 26 '14 at 20:06
  • with `list()` *and* `safe=False` it would work. But wouldn't it be better to pass a dict-object? Is there a way to convert `Genre.objects.values()` to a valid dict object? – speendo Sep 26 '14 at 20:09
  • 2
    @speendo: .values() returns a list of dictionaries. If you want you can create a new dictionary `dict(genres=Genre.objects...)` and use that instead. – Tiago Sep 26 '14 at 20:18
  • @Tiago thank you, but `>>> JsonResponse(dict(genres=Genre.objects.values('name', 'color')))` is also "not JSON serializable" :( – speendo Sep 26 '14 at 20:25
  • 1
    I'm just guessing here, but maybe now the `list()` will do the trick. `dict(genres=list(Genre.object...))`. – Tiago Sep 26 '14 at 20:27
  • 1
    @Tiago indeed. Is it just me, or is it ridiculously tricky to generate a JSON response object out of a django model? Anyway - would you like to formulate an answer so that I can accept it? – speendo Sep 26 '14 at 20:30

2 Answers2

24

For future reference, .values() returns a ValuesQuerySet that behaves like a iterable full of dictionaries, so using the list() will make a new instance of a list with all the dictionaries in it. With that, you can create a new dict and serialize that.

response = JsonResponse(dict(genres=list(Genre.objects.values('name', 'color'))))

IIRC, it's not safe to have a JSON object that has a list as root and that's probably why Django is complaining. I couldn't find any reference about that now to provide a source, sorry.

Tiago
  • 9,457
  • 5
  • 39
  • 35
  • 3
    The answer over at http://stackoverflow.com/a/26833156/179583 uses `serializers` from django.core, which might be handy to get the entire model out but seems odd/gross to have to pass already-serialized JSON to JsonResponse. I like this answer's technique better, especially for when I only need a few fields from each model. – natevw Jan 19 '16 at 18:51
  • I've been looking so long for an answer like this! – Vizjerei Feb 06 '17 at 18:56
  • In the serialize function you can pass an optional `fields` parameter which will only return the specified fields instead of the whole object as JSON. – Royalbishop101 Oct 16 '20 at 19:42
2

To pass nondictionary values to the JsonResponse as you retrieved with Genres.object.values('name','color') you can simple set the safe argument to false and it will return JSON.

from django.http import JsonResponse

def django_json(request):
    data = Genres.object.values('name','color')
    return JsonResponse(data, safe=False)

That should return a list of JSON of the values you specified. Check out my article How to Return a Json Response with Django for more detailed info on how this works.

Alternatively, if you would like to return a queryset back as JSON you can use Djangos core serializer like this:

from django.core.serializers import serialize
from django.http import JsonResponse
from .models import Genre

def django_models_json(request):
    qs = Genre.objects.all()
    data = serialize("json", qs, fields=('name', 'color'))
    return JsonResponse(data)

This will return the same as above.

Royalbishop101
  • 179
  • 2
  • 9