我有一个十进制('3.9')作为对象的一部分,并希望将其编码为一个JSON字符串,它应该看起来像{'x': 3.9}。我不关心客户端的精度,所以浮点数很好。
有什么好方法来序列化它吗?JSONDecoder不接受Decimal对象,提前转换为浮点数会产生{'x': 3.8999999999999999},这是错误的,而且会浪费大量带宽。
我有一个十进制('3.9')作为对象的一部分,并希望将其编码为一个JSON字符串,它应该看起来像{'x': 3.9}。我不关心客户端的精度,所以浮点数很好。
有什么好方法来序列化它吗?JSONDecoder不接受Decimal对象,提前转换为浮点数会产生{'x': 3.8999999999999999},这是错误的,而且会浪费大量带宽。
当前回答
子类化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)
其他回答
这个问题很老了,但是对于大多数用例,Python3中似乎有一个更好、更简单的解决方案:
number = Decimal(0.55)
converted_number = float(number) # Returns: 0.55 (as type float)
你可以把Decimal转换成float。
来自JSON标准文档,链接在json.org:
JSON is agnostic about the semantics of numbers. In any programming language, there can be a variety of number types of various capacities and complements, fixed or floating, binary or decimal. That can make interchange between different programming languages difficult. JSON instead offers only the representation of numbers that humans use: a sequence of digits. All programming languages know how to make sense of digit sequences even if they disagree on internal representations. That is enough to allow interchange.
因此,在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,首先要将其转换为字符串。
十进制不适合通过以下方式进行转换:
浮子由于精度问题 由于openapi的限制
我们仍然需要直接十进制到一个数字json序列化。
下面是@tesdal的fakfloat解决方案的扩展(在v3.5.2rc1中关闭)。 它使用fakestr + monkeypatching来避免引号和小数的“浮动”。
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开发人员强迫我们在不适合使用浮点数的地方使用浮点数。
基于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关键字参数),你只需要安装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')})
这个问题的原因是,请求只在simplejson存在时才使用它,如果它没有安装,则回落到内置json。