TypeError: Object of type LinkPreview is not JSON serializable

298 Views Asked by At

I'm using the following link preview python package in my django 3.2 api. https://github.com/meyt/linkpreview

When I post a link from my frontend flutter app and attempt to preview it, I get the error as stated.

TypeError: Object of type LinkPreview is not JSON serializable

Here are the views.py my flutter app hits :

class PreviewLink(APIView):
    permission_classes = (IsAuthenticated, IsNotSuspended)
    throttle_scope = 'link_preview'

    def post(self, request):
        serializer = PreviewLinkSerializer(data=request.data, context={"request": request})
        serializer.is_valid(raise_exception=True)
        data = serializer.validated_data

        link = data.get('link')
        user = request.user

        link_preview = user.preview_link(link)

        return Response(link_preview, status=status.HTTP_200_OK)

class LinkIsPreviewable(APIView):
    permission_classes = (IsAuthenticated, IsNotSuspended)
    throttle_scope = 'link_preview'

    def post(self, request):
        serializer = PreviewLinkSerializer(data=request.data, context={"request": request})
        serializer.is_valid(raise_exception=True)
        data = serializer.validated_data

        link = data.get('link')

        try:
            is_previewable = link_preview(url=link)
        except Exception as e:
            is_previewable = False

        return Response({
            'is_previewable': is_previewable
        }, status=status.HTTP_200_OK)

The PreviewLinkSerializer class --->

class PreviewLinkSerializer(serializers.Serializer):
    link = serializers.CharField(max_length=255, required=True, allow_blank=False)

The link_preview function:

def link_preview(url: str = None,content: str = None,parser: str = "html.parser"):
        
        """
        Get link preview
        """
        if content is None:
            try:
                grabber = LinkGrabber()
                content, url = grabber.get_content(url)
            except InvalidMimeTypeError:
                content = ""
    
        link = Link(url, content)
        return LinkPreview(link, parser=parser)

Here is the User class that contains preview_link():

def preview_link(self, link):
         return link_preview(url=link)

I have only pasted the relevant code above. The complete code is available in the link I shared.

1

There are 1 best solutions below

4
Ali Lotfi On

The problem

If we look into the error message, we might get some useful information:

TypeError: Object of type LinkPreview is not JSON serializable

Here we can see that:

  1. There is an object of type LinkPreview
  2. And it seems like we're trying to serialize it in a JSON

Looking at your code, I can see that the link_preview is returning a LinkPreview object.

Then we have (something like) this in your LinkIsPreviewable#post() method:

is_previewable = link_preview(url=link)
return Response({
    'is_previewable': is_previewable
}, status=status.HTTP_200_OK)

which means we're telling Django to serialize a mapping from str to LinkPreview, which it doesn't know how to do.

The solution

Based on the author's comment under the OP's question on GitHub, there is a LinkPreview#to_dict method which converts a LinkPreview instance to a serializable dict.

Therefore, if you only need the information returned by that method, you can simply use it.

This way, you'd only need to change one line for each method, so turn:

return Response(link_preview, status=status.HTTP_200_OK)

to

return Response(link_preview.to_dict(), status=status.HTTP_200_OK)

and

is_previewable = link_preview(url=link)

into

is_previewable = link_preview(url=link).to_dict()

The old solution

So how do we solve it?

We have to first serialize the is_previewable and then store it in the dict.

I wasn't able to find a serializer for the LinkPreview class in the GitHub repository you provided, therefore you have to write it yourself.

Something like these would do:

class LinkSerializer(serializers.Serializer):
    url = serializers.CharField()
    content = serializers.CharField()


class GenericSerializer(serializers.Serializer):
    title = serializers.CharField()
    description = serializers.CharField()
    image = serializers.CharField()


class LinkPreviewSerializer(serializers.Serializer):
    link = LinkSerializer()
    generic = GenericSerializer(allow_null=True)

Note that I only implemented it for the Generic type, you can do the same for the rest of the source types as well.

Then you can use it to serialize LinkPreview objects like this:

serialized_preview = LinkPreviewSerializer(instance=is_previewable).data

Now, the serialized_preview can be stored in a dict and be passed to the Response constructor.

Note: You didn't post the content of the User#preview_link() method, but if it returns a LinkPreview object too, you might need to serialize that object as well before passing it to the Response constructor.