Search code examples
pythondjangoserializationjson-deserialization

why am I getting TypeError: string indices must be integers, not 'str' on Django Deserialization?


I'm trying out using AJAX to implement tags for Book Blog posts. I want to use TDD to check and make sure that they filter properly but am running into issues. Mainly when I try and Deserialize the JSONResponse content, it throws an error when I try and iter through it.

error message:

  File "E:\04_projects\01_Python\10_book_blog\venv\Lib\site-packages\django\core\serializers\json.py", line 70, in Deserializer
    yield from PythonDeserializer(objects, **options)
  File "E:\04_projects\01_Python\10_book_blog\venv\Lib\site-packages\django\core\serializers\python.py", line 111, in Deserializer
    Model = _get_model(d["model"])
                       ~^^^^^^^^^
TypeError: string indices must be integers, not 'str'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "E:\04_projects\01_Python\10_book_blog\posts\tests\test_views.py", line 103, in test_returns_filtered_posts
    for obj in decereal:
  File "E:\04_projects\01_Python\10_book_blog\venv\Lib\site-packages\django\core\serializers\json.py", line 74, in Deserializer
    raise DeserializationError() from exc
django.core.serializers.base.DeserializationError

test case that makes the error:

def test_returns_filtered_posts(self):
        # makes one post with a tag and one post without
        t1 = Tag.objects.create(tag_name='test', group_name='general')
        p1 = Post.objects.create(book_title='test_book')
        p2 = Post.objects.create(book_title='test_book2')
        p1.tags.add(t1)

        # simulates clicking the 'test' tag
        response = self.client.post(reverse('ajax_post'), {'tag[]': ['test', 'general']})
        # decerealizes the data
        decereal = deserialize('json', response.content)
        # print for debugging
        for obj in decereal:
            print(obj)
        # bad test case but not getting this far
        self.assertNotIn(p2, decereal)

The view that is called:

def ajax_call(request):
    data = serializers.serialize('json', Post.objects.all())
    # this was just for testing
    uncereal = serializers.deserialize('json', data)
    return JsonResponse(data, safe=False)

I can iter through uncereal in ajax_call without issue, the problem comes in my test case when I try it the same way that they do in the docs https://docs.djangoproject.com/en/4.2/topics/serialization/#deserializing-data, I get an error. Any help would be appreciated. Thank you


Solution

  • Suggested fix:

    def ajax_call(request):
        serialized = serializers.serialize('json', Post.objects.all())
        return HttpResponse(serialized)
    

    Explanation expanded from my comments:

    I think the basic problem is that JsonResponse expects to receive a dict instance, but you are passing it a string of already serialized json. With safe=False it will accept other JSON-serializable types besides dict (e.g. list, string, number, None).

    So if you pass a string to serialize it will escape it and wrap in double quotes to make it a JSON string. So you have ended up with a JSON string of a JSON value serialized to string.

    Then when you come to deserialize the response it's trying to inflate that back into model instances e.g. expects d to be a dict but it's just a string.

    So the simplest fix is just to stop double-encoding things in your view function.

    You want to use serializers.serialize rather than JsonResponse to do the serialization, because the former understands how to serialize model instances. Having serialized to a string of JSON you can then return that string with an ordinary HttpResponse.