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

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


当前回答

我尝试在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]'

其他回答

如果你想传递一个包含小数的字典到请求库(使用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。

对于那些不想使用第三方库的人…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}'

我尝试在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]'

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

浮子由于精度问题 由于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开发人员强迫我们在不适合使用浮点数的地方使用浮点数。

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