Skip to content

before

django3.2 + win10 + python3.9

今天遇到一个同学问这样一个django问题: 1832669367834247168.png 代码很简单,从表中取出数据,然后以json的形式返回给前端,但是报了序列化的错。 问题产生的原因就是,django中,从表中取出来的数据都是queryset格式,但json无法序列这种格式,导致的报错。 所以,我把解决方法也贴出来。 这里贴出表结构和路由:

python
# urls.py
from django.contrib import admin
from django.urls import path
from app import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('index/', views.index),
]

# models.py
from django.db import models


class User(models.Model):
    user = models.CharField(max_length=32)
    pwd = models.CharField(max_length=32)
    def __str__(self):
        return self.user

自己插入几条数据就完了。 1832669368605999104.png 我们接下来主要看在views.py中怎么处理。 当然再说怎么处理之前,先来做个铺垫,就是遇到json无法序列化的类型怎么办? 那些json.dumps无法序列化的类型,需要我们手动处理,参考:

python
import json
import decimal
from datetime import date, datetime


class CustomJsonEncoder(json.JSONEncoder):
    def default(self, field):
        if isinstance(field, date):
            return field.strftime('%Y-%m-%d')
        elif isinstance(field, datetime):
            return field.strftime('%Y-%m-%d %H:%M:%S')
        elif isinstance(field, decimal.Decimal):
            return float(field)
        # 如果有更多json无法序列化的类型,就继续elif手动处理
        else:  # 其他json能序列化的就调用父类的default方法就完了
            return json.JSONEncoder.default(self, field)

data_list = [
    {"name": "zhangkai1", "age": 18, "ctime": datetime.now()},
    {"name": "zhangkai2", "age": 18, "ctime": datetime.now()},
]
# 默认的json无法序列化时间,日期类型的数据
# print(json.dumps(data_list))  # TypeError: Object of type datetime is not JSON serializable

# 所以需要我们手动处理这些数据
print(json.dumps(data_list, cls=CustomJsonEncoder))
"""
[
    {"name": "zhangkai1", "age": 18, "ctime": "2021-12-30"}, 
    {"name": "zhangkai2", "age": 18, "ctime": "2021-12-30"
]
"""

PS:如果不懂那个自定义的类的,可以翻翻官档:https://docs.python.org/zh-cn/3.9/library/json.html?highlight=json#module-json进一步了解下。

方案1

手动处理queryset,整理后返回给前端。

python
from django.shortcuts import render, HttpResponse
from django.http import JsonResponse
from django.core import serializers
from app.models import User


def index(request):
    user_query_set = User.objects.all()
    user_list = [
        {"user": user.user, "pwd": user.pwd, "pk": user.pk}
        for user in user_query_set
    ]
    # 因为返回的是列表,所以要加上safe,ensure_ascii让浏览器页面中的中文正常显示
    return JsonResponse(user_list, safe=False, json_dumps_params={'ensure_ascii':False})

前端拿到的也是json数据(content-type:application/json):

json
[
    {
        "user": "zhangkai",
        "pwd": "123",
        "pk": 1
    },
    {
        "user": "likai",
        "pwd": "123",
        "pk": 2
    }
]

这种解决方式挺好的,但是也有点麻烦,就是字段多了需要手动一个个写。

方案2

使用django内置的序列化组件serializers,它可以序列化xmljsonyaml类型的数据。

python
from django.shortcuts import render, HttpResponse
from django.http import JsonResponse
from django.core import serializers
from app.models import User


def index(request):
    user_query_set = User.objects.all()
    data_list = serializers.serialize('json', user_query_set)
    # data_list = serializers.serialize('json', user_query_set, fields=("user", "pwd"))
    # return HttpResponse(data_list)
    # 你要是直接这么返回,也没问题,但是默认返回的数据类型是文本类型content-type:text/html; charset=utf-8
    # 而且,这么返回,默认也把表名之类的都返回了,也不太好
    """
    [
        {"model": "app.user", "pk": 1, "fields": {"user": "zhangkai", "pwd": "123"}}, 
        {"model": "app.user", "pk": 2, "fields": {"user": "likai", "pwd": "123"}}
    ]
    """
    # 返回时,加上content_type表示返回的是json类型的数据,content-type:application/json
    # 但依然存在上面的问题
    return HttpResponse(data_list, content_type="application/json")

但也不推荐这种方法,这种方法存在跨表查询问题。

方案3

使用json和values结合,也推荐这种方式。

python
import json
import decimal
from datetime import date, datetime
from django.shortcuts import render, HttpResponse
from django.http import JsonResponse
from django.core import serializers
from app.models import User


class CustomJsonEncoder(json.JSONEncoder):
    def default(self, field):
        if isinstance(field, date):
            return field.strftime('%Y-%m-%d')
        elif isinstance(field, datetime):
            return field.strftime('%Y-%m-%d %H:%M:%S')
        elif isinstance(field, decimal.Decimal):
            return float(field)
        # 如果有更多json无法序列化的类型,就继续elif手动处理
        else:  # 其他json能序列化的就调用父类的default方法就完了
            return json.JSONEncoder.default(self, field)

def index(request):
    # user_query_set = User.objects.values()
    user_query_set = User.objects.values("user", "pwd")  # 你也可以指定返回哪些字段
    # 虽然使用values,将数据变成了queryset[{}, {}],但本质上还是个queryset
    data_list = list(user_query_set)
    data_list = json.dumps(data_list, cls=CustomJsonEncoder)  # 遇到无法序列化的字段,自己处理
    # 这样返回的就是json类型的数据了content-type:application/json
    return HttpResponse(data_list, content_type="application/json")

方案4

使用django自带的模型转字典方法。

python
import json
from datetime import date, datetime
from django.shortcuts import render, HttpResponse
from django.http import JsonResponse
from django.core import serializers
from django.forms.models import model_to_dict
from app.models import User


class CustomJsonEncoder(json.JSONEncoder):
    def default(self, field):
        if isinstance(field, date):
            return field.strftime('%Y-%m-%d')
        elif isinstance(field, datetime):
            return field.strftime('%Y-%m-%d %H:%M:%S')
        # 如果有更多json无法序列化的类型,就继续elif手动处理
        else:  # 其他json能序列化就调用父类的default方法就完了
            return json.JSONEncoder.default(self, field)


def index(request):
    user_query_set = User.objects.all()
    data_list = [model_to_dict(i) for i in user_query_set]
    data_list = json.dumps(data_list, cls=CustomJsonEncoder)  # 遇到无法序列化的字段,自己处理
    # 这样返回的就是json类型的数据了content-type:application/json
    """
    [
        {
            "id": 1,
            "user": "zhangkai",
            "pwd": "123"
        },
        {
            "id": 2,
            "user": "likai",
            "pwd": "123"
        }
    ]
    """
    return HttpResponse(data_list, content_type="application/json")

方案5

使用django-restful-framework框架的序列化器进行返回,参考序列化器

that's all, see also:

Django:序列化的几种方法 | 序列化 serializers