我有一个十进制('3.9')作为对象的一部分,并希望将其编码为一个JSON字符串,它应该看起来像{'x': 3.9}。我不关心客户端的精度,所以浮点数很好。
有什么好方法来序列化它吗?JSONDecoder不接受Decimal对象,提前转换为浮点数会产生{'x': 3.8999999999999999},这是错误的,而且会浪费大量带宽。
我有一个十进制('3.9')作为对象的一部分,并希望将其编码为一个JSON字符串,它应该看起来像{'x': 3.9}。我不关心客户端的精度,所以浮点数很好。
有什么好方法来序列化它吗?JSONDecoder不接受Decimal对象,提前转换为浮点数会产生{'x': 3.8999999999999999},这是错误的,而且会浪费大量带宽。
当前回答
我的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}
这里“聪明”的事情是使用default将Decimal自动转换为int或float,利用eval函数:
但是在你的代码上使用eval时一定要小心,因为这可能会导致安全问题;)
其他回答
我只有美元!
我扩展了一堆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
让我的生活轻松多了…
子类化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)
我尝试在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]'
3.9不能准确地在IEEE浮点数中表示,它总是以3.899999999999999999表示,例如,尝试打印repr(3.9),你可以在这里阅读更多信息:
http://en.wikipedia.org/wiki/Floating_point http://docs.sun.com/source/806-3568/ncg_goldberg.html
所以如果你不想要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)
对于那些不想使用第三方库的人…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}'