我有一个十进制('3.9')作为对象的一部分,并希望将其编码为一个JSON字符串,它应该看起来像{'x': 3.9}。我不关心客户端的精度,所以浮点数很好。
有什么好方法来序列化它吗?JSONDecoder不接受Decimal对象,提前转换为浮点数会产生{'x': 3.8999999999999999},这是错误的,而且会浪费大量带宽。
我有一个十进制('3.9')作为对象的一部分,并希望将其编码为一个JSON字符串,它应该看起来像{'x': 3.9}。我不关心客户端的精度,所以浮点数很好。
有什么好方法来序列化它吗?JSONDecoder不接受Decimal对象,提前转换为浮点数会产生{'x': 3.8999999999999999},这是错误的,而且会浪费大量带宽。
当前回答
在我的Flask应用程序中,它使用python 2.7.11, Flask alchemy(具有'db.decimal'类型)和Flask Marshmallow(用于'即时'序列化器和反序列化器),我有这个错误,每次我做GET或POST。序列化器和反序列化器未能将十进制类型转换为任何可识别的JSON格式。
然后,我做了一个“pip install simplejson” 只要加上
import simplejson as json
序列化器和反序列化器又开始工作了。我什么也没做…… 十进制显示为'234.00'浮点格式。
其他回答
对于那些不想使用第三方库的人…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用户:
最近遇到TypeError: Decimal('2337.00')不是JSON可序列化的 当JSON编码时,即JSON .dump (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)
这是我从我们的课程中提取出来的
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)
来自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开发人员强迫我们在不适合使用浮点数的地方使用浮点数。