我有一个十进制('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时一定要小心,因为这可能会导致安全问题;)

其他回答

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

So:

>>> json.dumps(Decimal('3.9'))
'3.9'

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

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

我将分享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)

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

浮子由于精度问题 由于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.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)