Django QuerySet转列表annotate的Decimal字段JSON序列化异常修复
你的判断完全正确,问题出在Decimal类型的JSON序列化兼容上。
Django的Sum聚合函数在处理数值字段时默认返回Decimal类型,而Python标准库自带的JSON序列化器不支持该类型,序列化过程中无法正常转换就会直接输出null,就会出现终端打印queryset值正常、前端拿到的combined_quantity为空的现象。
你可以根据自己的业务场景选下面任意一种方案:
方案1:查询阶段直接转成浮点型(改动最小,快速生效)
用Django内置的Cast函数,在ORM查询时就把Sum结果转换为浮点类型,查出来的结果直接是普通float值,不需要额外调整序列化逻辑:
首先导入依赖:
from django.db.models import FloatField from django.db.models.functions import Cast
修改原有查询语句,给Sum计算套上类型转换:
queryset = list( Products.objects.filter(status=True, salelog__created__range=(start, today)) .values("name") .annotate(individual_sales=Count("salelog__quantity")) .annotate(combined_quantity=Cast(Sum("salelog__quantity"), output_field=FloatField())) )
改完直接用原有JsonResponse返回即可,前端拿到的就是标准数字类型。
注意:浮点类型存在二进制精度丢失问题,如果你的业务是金额类对精度要求极高,不要用这个方案,选后面两种。
方案2:指定支持Decimal的JSON编码器(通用方案,无精度损失)
不需要修改ORM查询逻辑,只需要在返回响应时指定支持Decimal类型的JSON编码器即可。
Django本身自带了支持Decimal、datetime、UUID等特殊类型的编码器DjangoJSONEncoder,不需要自己写代码,直接引入使用就行:
from django.http import JsonResponse from django.core.serializers.json import DjangoJSONEncoder # 原有查询逻辑完全不变 return JsonResponse(queryset, safe=False, encoder=DjangoJSONEncoder)
这个自带编码器默认会把Decimal转成字符串返回,完全不会丢失精度。如果需要直接返回数字类型,也可以自己写个简单的编码器:
import json from decimal import Decimal class CustomDecimalEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, Decimal): return float(obj) # 要无精度损失就返回str(obj) return super().default(obj)
返回时把encoder参数换成你自己写的CustomDecimalEncoder即可。
如果项目里大量接口都要处理Decimal类型,可以去settings.py里配置全局默认的JSON编码器,不用每次写接口都传encoder参数。
方案3:用DRF序列化器(集成了DRF的项目推荐用)
如果你的项目已经用了Django REST Framework,直接定义对应序列化类处理字段类型即可,DRF自带的DecimalField支持灵活配置返回格式、精度校验:
from rest_framework import serializers class SalesStatsSerializer(serializers.Serializer): name = serializers.CharField() individual_sales = serializers.IntegerField() # coerce_to_string=False返回数字类型,True返回字符串类型 combined_quantity = serializers.DecimalField(max_digits=10, decimal_places=2, coerce_to_string=False)
查询完成后用序列化器做转换再返回:
from rest_framework.response import Response serializer = SalesStatsSerializer(queryset, many=True) return Response(serializer.data)
如果你选择把Decimal转成字符串返回,Vue侧做数值计算前先用Number(val)或者parseFloat(val)做一次类型转换,避免出现字符串拼接的错误。
内容的提问来源于stack exchange,提问作者Niguel Gunther

