在Django中将模型序列化为带有正确小数类型的JSON

2 投票
1 回答
4290 浏览
提问于 2025-04-17 06:52

这里有一个示例模型:

class FooModel(models.Model):
    foo = models.DecimalField(max_digits=6, decimal_places=3, null=True)

序列化:

from django.core import serializers
obj = get_object_or_404(FooModel, pk=1)
data = serializers.serialize("json", [obj])

这将返回类似于:

[
    {
        "pk": 1,
        "model": "app.foomodel",
        "fields": {
            "foo": "50"
        }
    }
]

问题

我该如何让 foo 字段以浮点数的形式序列化,而不是字符串。我不想使用浮点模型类型,因为浮点数有时候不能正确存储小数。

提前谢谢你。

1 个回答

4

如果值是 5050.0,它们有什么区别呢?一个 Decimal 对象会保留你最开始输入的内容,比如 Decimal('50') 会得到 50

>>> from decimal import Decimal
>>> d = Decimal('50')
>>> print d
50

不过,传给 DecimalField 构造函数的参数只是用来限制 存储 值的,而不是用来显示这些值的。

现在,因为 Django 使用的是标准的 json/simplejson 库,所以在序列化时,你可以指定一个自定义的编码器,就像在 这个问题 中提到的那样:

import decimal
import json
class DecimalEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, decimal.Decimal):
            return '%.2f' % obj # Display Decimal obj as float
        return json.JSONEncoder.default(self, obj)

但事情并没有那么简单。正如 这篇博客文章 中详细说明的,Django 明确地将 cls=DjangoJSONEncoder 传递给 simplejson.dump(...),所以我们需要绕过这个限制,创建一个自定义的序列化对象,引用我们之前创建的 DecimalEncoder

from django.core.serializers.json import Serializer as JSONSerializer
class DecimalSerializer(JSONSerializer):
    def end_serialization(self):
        self.options.pop('stream', None)
        self.options.pop('fields', None)
        json.dump(self.objects, self.stream, cls=DecimalEncoder, **self.options)

接下来,你需要实例化 DecimalSerializer 作为你自己的序列化对象,然后就会出现一些神奇的事情:

my_serializer = DecimalSerializer()
print my_serializer.serialize([obj], indent=4)

这样就会得到:

[
    {
        "pk": 1, 
        "model": "app.foomodel", 
        "fields": {
            "foo": "50.00"
        }
    }
]

这看起来工作量很大。其实使用 Django 的 模型验证 来确保 FooModel.foo 字段始终是浮点数,可能会更简单一些,这里是一个粗暴的尝试:

from django.core.exceptions import ValidationError

class FooModel(models.Model):
    foo = models.DecimalField(max_digits=6, decimal_places=3, null=True)

    def clean(self):
        if '.' not in str(self.foo):
            raise ValidationError('Input must be float!')

    def save(self, *args, **kwargs):
        self.full_clean()
        super(FooModel, self).save(*args, **kwargs)

然后:

>>> f = FooModel(foo='1')
>>> f.save()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/home/jathan/sandbox/foo/app/models.py", line 15, in save
    self.full_clean()
  File "/usr/local/lib/python2.6/dist-packages/django/db/models/base.py", line 828, in full_clean
    raise ValidationError(errors)
ValidationError: {'__all__': [u'Input must be float!']}

撰写回答