Python JSON序列化一个Decimal对象

时间:2009-12-25 05:00:13

标签: python json floating-point decimal

我有Decimal('3.9')作为对象的一部分,并希望将其编码为JSON字符串,该字符串应该看起来像{'x': 3.9}。我不关心客户端的精度,所以浮动很好。

有序列化的好方法吗? JSONDecoder不接受Decimal对象,并且事先转换为float会产生{'x': 3.8999999999999999},这是错误的,并且会大大浪费带宽。

17 个答案:

答案 0 :(得分:196)

Simplejson 2.1及更高版本具有对Decimal类型的原生支持:

>>> json.dumps(Decimal('3.9'), use_decimal=True)
'3.9'

请注意,use_decimal默认为True

def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
    allow_nan=True, cls=None, indent=None, separators=None,
    encoding='utf-8', default=None, use_decimal=True,
    namedtuple_as_object=True, tuple_as_array=True,
    bigint_as_string=False, sort_keys=False, item_sort_key=None,
    for_json=False, ignore_nan=False, **kw):

所以:

>>> json.dumps(Decimal('3.9'))
'3.9'

希望此功能将包含在标准库中。

答案 1 :(得分:150)

我想让大家知道我在运行Python 2.6.5的网络服务器上尝试了MichałMarczyk的答案,它运行良好。但是,我升级到Python 2.7并且它停止了工作。我试着想一些编码Decimal对象的方法,这就是我想出的:

class DecimalEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, decimal.Decimal):
            return float(o)
        return super(DecimalEncoder, self).default(o)

这应该有助于任何遇到Python 2.7问题的人。我测试了它,似乎工作正常。如果有人注意到我的解决方案中的任何错误或提出了更好的方法,请告诉我。

答案 2 :(得分:122)

如何对json.JSONEncoder进行子类化?

class DecimalEncoder(json.JSONEncoder):
    def _iterencode(self, o, markers=None):
        if isinstance(o, decimal.Decimal):
            # wanted a simple yield str(o) in the next line,
            # but that would mean a yield on the line with super(...),
            # which wouldn't work (see my comment below), so...
            return (str(o) for o in [o])
        return super(DecimalEncoder, self)._iterencode(o, markers)

然后像这样使用它:

json.dumps({'x': decimal.Decimal('5.5')}, cls=DecimalEncoder)

答案 3 :(得分:27)

在我的Flask应用程序中,它使用python 2.7.11,flask alchemy(带有' db.decimal'类型)和Flask Marshmallow(用于' instant'序列化器和反序列化器),我有这个错误,每次我做GET或POST。序列化程序和反序列化程序无法将Decimal类型转换为任何JSON可识别格式。

我做了一个" pip install simplejson"然后 只需添加

import simplejson as json

序列化器和反序列化器再次开始发出呜呜声。我什么都没做...... DEciamls显示为' 234.00'浮动格式。

答案 4 :(得分:26)

我尝试从简单json切换到内置json用于GAE 2.7,并且存在小数问题。如果default返回str(o)则有引号(因为_iterencode在默认结果上调用_iterencode),而float(o)将删除尾随0。

如果default返回一个继承自float的类的对象(或任何调用repr而没有额外格式化的东西)并且有一个自定义的__repr__方法,它似乎就像我想要的那样工作。

import json
from decimal import Decimal

class fakefloat(float):
    def __init__(self, value):
        self._value = value
    def __repr__(self):
        return str(self._value)

def defaultencode(o):
    if isinstance(o, Decimal):
        # Subclass float with custom repr?
        return fakefloat(o)
    raise TypeError(repr(o) + " is not JSON serializable")

json.dumps([10.20, "10.20", Decimal('10.20')], default=defaultencode)
'[10.2, "10.20", 10.20]'

答案 5 :(得分:13)

缺少原生选项,所以我会为下一个寻找它的人/胆添加它。

从Django 1.7.x开始,有一个内置的DjangoJSONEncoder,您可以从django.core.serializers.json获取它。

import json
from django.core.serializers.json import DjangoJSONEncoder
from django.forms.models import model_to_dict

model_instance = YourModel.object.first()
model_dict = model_to_dict(model_instance)

json.dumps(model_dict, cls=DjangoJSONEncoder)

的Presto!

答案 6 :(得分:11)

3.9无法在IEEE浮点数中准确表示,它始终为3.8999999999999999,例如试试print repr(3.9),你可以在这里阅读更多相关内容:

http://en.wikipedia.org/wiki/Floating_point
http://docs.sun.com/source/806-3568/ncg_goldberg.html

因此,如果您不想浮动,只需要将其作为字符串发送,并允许自动将十进制对象转换为JSON,请执行以下操作:

import decimal
from django.utils import simplejson

def json_encode_decimal(obj):
    if isinstance(obj, decimal.Decimal):
        return str(obj)
    raise TypeError(repr(obj) + " is not JSON serializable")

d = decimal.Decimal('3.5')
print simplejson.dumps([d], default=json_encode_decimal)

答案 7 :(得分:10)

我的$ .02!

我扩展了一堆JSON编码器,因为我为我的Web服务器序列化了大量数据。这是一些不错的代码。请注意,它很容易扩展到您想要的任何数据格式,并将重现3.9 "thing": 3.9

JSONEncoder_olddefault = json.JSONEncoder.default
def JSONEncoder_newdefault(self, o):
    if isinstance(o, UUID): return str(o)
    if isinstance(o, datetime): return str(o)
    if isinstance(o, time.struct_time): return datetime.fromtimestamp(time.mktime(o))
    if isinstance(o, decimal.Decimal): return str(o)
    return JSONEncoder_olddefault(self, o)
json.JSONEncoder.default = JSONEncoder_newdefault

让我的生活变得更加轻松......

答案 8 :(得分:6)

这就是我从课堂中提取的内容

class CommonJSONEncoder(json.JSONEncoder):

    """
    Common JSON Encoder
    json.dumps(myString, cls=CommonJSONEncoder)
    """

    def default(self, obj):

        if isinstance(obj, decimal.Decimal):
            return {'type{decimal}': str(obj)}

class CommonJSONDecoder(json.JSONDecoder):

    """
    Common JSON Encoder
    json.loads(myString, cls=CommonJSONEncoder)
    """

    @classmethod
    def object_hook(cls, obj):
        for key in obj:
            if isinstance(key, six.string_types):
                if 'type{decimal}' == key:
                    try:
                        return decimal.Decimal(obj[key])
                    except:
                        pass

    def __init__(self, **kwargs):
        kwargs['object_hook'] = self.object_hook
        super(CommonJSONDecoder, self).__init__(**kwargs)

通过unittest:

def test_encode_and_decode_decimal(self):
    obj = Decimal('1.11')
    result = json.dumps(obj, cls=CommonJSONEncoder)
    self.assertTrue('type{decimal}' in result)
    new_obj = json.loads(result, cls=CommonJSONDecoder)
    self.assertEqual(new_obj, obj)

    obj = {'test': Decimal('1.11')}
    result = json.dumps(obj, cls=CommonJSONEncoder)
    self.assertTrue('type{decimal}' in result)
    new_obj = json.loads(result, cls=CommonJSONDecoder)
    self.assertEqual(new_obj, obj)

    obj = {'test': {'abc': Decimal('1.11')}}
    result = json.dumps(obj, cls=CommonJSONEncoder)
    self.assertTrue('type{decimal}' in result)
    new_obj = json.loads(result, cls=CommonJSONDecoder)
    self.assertEqual(new_obj, obj)

答案 9 :(得分:6)

对于那些不想使用第三方库的人来说... Elias Zamaria的问题是它会转换为float,这可能会遇到问题。例如:

>>> json.dumps({'x': Decimal('0.0000001')}, cls=DecimalEncoder)
'{"x": 1e-07}'
>>> json.dumps({'x': Decimal('100000000000.01734')}, cls=DecimalEncoder)
'{"x": 100000000000.01733}'

JSONEncoder.encode()不同,JSONEncoder.default()方法使您可以返回原义的json内容,而encode()则可以返回json兼容类型(例如float),然后以常规方式对其进行编码。 import json from collections.abc import Mapping, Iterable from decimal import Decimal class DecimalEncoder(json.JSONEncoder): def encode(self, obj): if isinstance(obj, Mapping): return '{' + ', '.join(f'{self.encode(k)}: {self.encode(v)}' for (k, v) in obj.items()) + '}' if isinstance(obj, Iterable) and (not isinstance(obj, str)): return '[' + ', '.join(map(self.encode, obj)) + ']' if isinstance(obj, Decimal): return f'{obj.normalize():f}' # using normalize() gets rid of trailing 0s, using ':f' prevents scientific notation return super().encode(obj) 的问题在于,它(通常)仅在顶层运行。但是它仍然可以使用,但需要做一些额外的工作(python 3.x):

>>> json.dumps({'x': Decimal('0.0000001')}, cls=DecimalEncoder)
'{"x": 0.0000001}'
>>> json.dumps({'x': Decimal('100000000000.01734')}, cls=DecimalEncoder)
'{"x": 100000000000.01734}'

哪个给你:

docker-compose

答案 10 :(得分:4)

来自JSON Standard Document中的链接json.org

  

JSON对数字的语义不可知。在任何编程语言中,都可以有多种   各种容量和补码的数量类型,固定或浮动,二进制或十进制。那可以   难以在不同编程语言之间交换。相反,JSON仅提供表示   人类使用的数字:一系列数字。所有编程语言都知道如何理解数字   即使他们在内部表征上存在分歧,也会产生序列。这足以让人们互换。

因此,在JSON中将十进制表示为数字(而不是字符串)实际上是准确的。贝娄是解决问题的可能方法。

定义自定义JSON编码器:

import json


class CustomJsonEncoder(json.JSONEncoder):

    def default(self, obj):
        if isinstance(obj, Decimal):
            return float(obj)
        return super(CustomJsonEncoder, self).default(obj)

然后在序列化数据时使用它:

json.dumps(data, cls=CustomJsonEncoder)

从对其他答案的评论中可以看出,旧版本的python在转换为float时可能会搞乱表示,但情况不再如此。

要在Python中获取小数:

Decimal(str(value))

此解决方案在Python 3.0 documentation on decimals中暗示:

  

要从float创建一个Decimal,首先将其转换为字符串。

答案 11 :(得分:2)

根据stdOrgnlDave回答,我已经定义了这个包装器,可以使用可选种类调用它,因此编码器只能用于项目中的某些类型。我相信这项工作应该在你的代码中完成,而不是使用这个"默认"编码器自"它比隐式"更明确,但我明白使用它会节省你的一些时间。 : - )

import time
import json
import decimal
from uuid import UUID
from datetime import datetime

def JSONEncoder_newdefault(kind=['uuid', 'datetime', 'time', 'decimal']):
    '''
    JSON Encoder newdfeault is a wrapper capable of encoding several kinds
    Use it anywhere on your code to make the full system to work with this defaults:
        JSONEncoder_newdefault()  # for everything
        JSONEncoder_newdefault(['decimal'])  # only for Decimal
    '''
    JSONEncoder_olddefault = json.JSONEncoder.default

    def JSONEncoder_wrapped(self, o):
        '''
        json.JSONEncoder.default = JSONEncoder_newdefault
        '''
        if ('uuid' in kind) and isinstance(o, uuid.UUID):
            return str(o)
        if ('datetime' in kind) and isinstance(o, datetime):
            return str(o)
        if ('time' in kind) and isinstance(o, time.struct_time):
            return datetime.fromtimestamp(time.mktime(o))
        if ('decimal' in kind) and isinstance(o, decimal.Decimal):
            return str(o)
        return JSONEncoder_olddefault(self, o)
    json.JSONEncoder.default = JSONEncoder_wrapped

# Example
if __name__ == '__main__':
    JSONEncoder_newdefault()

答案 12 :(得分:1)

您可以根据需要创建自定义JSON编码器。

import json
from datetime import datetime, date
from time import time, struct_time, mktime
import decimal

class CustomJSONEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, datetime):
            return str(o)
        if isinstance(o, date):
            return str(o)
        if isinstance(o, decimal.Decimal):
            return float(o)
        if isinstance(o, struct_time):
            return datetime.fromtimestamp(mktime(o))
        # Any other serializer if needed
        return super(CustomJSONEncoder, self).default(o)

解码器可以这样称呼,

import json
from decimal import Decimal
json.dumps({'x': Decimal('3.9')}, cls=CustomJSONEncoder)

,输出将是:

>>'{"x": 3.9}'

答案 13 :(得分:0)

对于任何想要快速解决方案的人来说,我是如何从Django查询中删除Decimal的

total_development_cost_var = process_assumption_objects.values('total_development_cost').aggregate(sum_dev = Sum('total_development_cost', output_field=FloatField()))
total_development_cost_var = list(total_development_cost_var.values())
  • 第1步:在查询中使用,output_field = FloatField()
  • 第2步:使用列表,例如list(total_development_cost_var.values())

希望有帮助

答案 14 :(得分:0)

对于Django用户

最近遇到TypeError: Decimal('2337.00') is not JSON serializable 而JSON编码即json.dumps(data)

解决方案

# converts Decimal, Datetime, UUIDs to str for Encoding
from django.core.serializers.json import DjangoJSONEncoder  

json.dumps(response.data, cls=DjangoJSONEncoder)

但是,现在Decimal值将是一个字符串,现在我们可以使用parse_float中的json.loads选项在解码数据时显式设置十进制/浮点值解析器:

import decimal 

data = json.loads(data, parse_float=decimal.Decimal) # default is float(num_str)

答案 15 :(得分:0)

如果您想将包含小数的字典传递到requests库(使用json关键字参数),您只需安装simplejson

$ pip3 install simplejson    
$ python3
>>> import requests
>>> from decimal import Decimal
>>> # This won't error out:
>>> requests.post('https://www.google.com', json={'foo': Decimal('1.23')})

问题的原因是requests仅在simplejson存在时使用json,如果未安装,则会回退到内置Except()

答案 16 :(得分:-6)

这可以通过添加

来完成
    elif isinstance(o, decimal.Decimal):
        yield str(o)
\Lib\json\encoder.py:JSONEncoder._iterencode中的

,但我希望有更好的解决方案