Python JSON序列化Decimal对象

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

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

331232 次浏览

3.9不能准确地在IEEE浮点数中表示,它总是以3.8999999999999999表示,例如,尝试print repr(3.9),你可以在这里阅读更多关于它的信息:

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

所以如果你不想要float,唯一的选项你必须将它作为字符串发送,并允许自动将十进制对象转换为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)

子类化json.JSONEncoder怎么样?

class DecimalEncoder(json.JSONEncoder):
def default(self, o):
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).default(o)

然后像这样使用它:

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

Simplejson 2.1和更高版本对十进制类型有原生支持:

>>> 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'

希望该特性将包含在标准库中。

我想让每个人都知道,我在运行Python 2.6.5的网络服务器上尝试了michaowarmarczyk的答案,它工作得很好。然而,我升级到Python 2.7,它停止工作了。我试着想一些方法来编码十进制对象,这就是我想到的:

import decimal


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

注意,这将把小数转换为它的字符串表示形式(例如;"1.2300"),以a.不丢失有效数字和b.防止舍入错误。

希望这能帮助任何在使用Python 2.7时遇到问题的人。我测试过了,它似乎工作得很好。如果有人在我的解决方案中注意到任何错误或提出了更好的方法,请告诉我。

使用的例子:

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

我尝试在GAE 2.7中从simplejson切换到内置json,在小数方面有问题。如果default返回str(o),则有引号(因为_iterencode对default的结果调用_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]'

这是我从我们的课程中提取出来的

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)

在我的Flask应用程序中,它使用python 2.7.11, Flask alchemy(具有'db.decimal'类型)和Flask Marshmallow(用于'即时'序列化器和反序列化器),我有这个错误,每次我做GET或POST。序列化器和反序列化器未能将十进制类型转换为任何可识别的JSON格式。

我做了一个“pip install simplejson”,然后 只要加上

import simplejson as json

序列化器和反序列化器又开始咕噜声了。我什么也没做…… 十进制显示为'234.00'浮点格式

我只有美元!

我扩展了一堆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

让我的生活轻松多了…

基于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()

JSON标准文档中,如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文档中暗示:

要从浮点数创建Decimal,首先要将其转换为字符串。

如果你想将一个包含小数的字典传递给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

原生Django选项缺失了,所以我将为下一个寻找它的guy/gall添加它。

从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)

您看!

您可以根据需要创建一个自定义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}'

对于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值将是一个字符串,现在我们可以在解码数据时显式设置Decimal /float值解析器,使用json.loads中的parse_float选项:

import decimal


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

对于那些不想使用第三方库的人…Elias Zamaria的答案的一个问题是,它转换为浮动,这可能会遇到问题。例如:

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

JSONEncoder.encode()方法让你返回json的文字内容,不像JSONEncoder.default(),它让你返回一个json兼容的类型(比如float),然后以正常的方式进行编码。encode()的问题是它(通常)只在顶层工作。但它仍然可用,只需要做一些额外的工作(python 3.x):

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)

这就给了你:

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

对于任何想要快速解决的人来说,这里是我如何从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:使用列表eg list(total_development_cost_var.values())

希望能有所帮助

这个问题很老了,但是对于大多数用例,Python3中似乎有一个更好、更简单的解决方案:

number = Decimal(0.55)
converted_number = float(number) # Returns: 0.55 (as type float)

你可以直接将Decimal转换为float

如果有人还在寻找答案,那么很有可能您的数据中有一个“NaN”,您正在尝试对其进行编码。因为NaN被Python视为浮点数。

我的2美分简单的解决方案,如果你确定Decimal是你的json dumps方法中唯一的坏人:

print(json.loads(json.dumps({
'a': Decimal(1230),
'b': Decimal(11111111123.22),
}, default=lambda x: eval(str(x)))))


>>> {'a': 1230, 'b': 11111111123.22}

“smart"这里的事情是使用default将Decimal自动转换为int或float,利用eval函数:默认值=lambda x: eval(str(x))

但是在你的代码上使用eval时一定要小心,因为这可能会导致安全问题;)

十进制不适合通过以下方式进行转换:

  • float由于精度问题
  • str由于openapi的限制

我们仍然需要直接十进制到一个数字json序列化。

这是@tesdal的fakfloat解决方案的扩展(在v3.5.2rc1中关闭)。 它使用fakestr + monkeypatching来避免报价和“浮动”;小数。< / p >
import json.encoder
from decimal import Decimal




def encode_fakestr(func):
def wrap(s):
if isinstance(s, fakestr):
return repr(s)
return func(s)
return wrap




json.encoder.encode_basestring = encode_fakestr(json.encoder.encode_basestring)
json.encoder.encode_basestring_ascii = encode_fakestr(json.encoder.encode_basestring_ascii)




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




class DecimalJsonEncoder(json.encoder.JSONEncoder):
def default(self, o):
if isinstance(o, Decimal):
return fakestr(o)
return super().default(o)




json.dumps([Decimal('1.1')], cls=DecimalJsonEncoder)


[1.1]

我不明白为什么python开发人员强迫我们在不适合使用浮点数的地方使用浮点数。

我将分享flask 2.1.0对我有用的东西 当我创建字典时,必须从jsonify使用,我使用舍入:

json_dict['price'] = round(self.price, ndigits=2) if self.price else 0

这样我就可以返回D.DD号或0,而不需要使用全局配置。这很好,因为有些十进制坐标需要更大,比如经纬度坐标。

return jsonify(json_dict)