Python JSON序列化一个Decimal对象

Kni*_*nio 207 python floating-point json decimal

我有一个Decimal('3.9')对象的一部分,并希望将其编码为一个看起来像的JSON字符串{'x': 3.9}.我不关心客户端的精度,所以浮动很好.

是否有一种很好的方法来序列化这个?JSONDecoder不接受Decimal对象,并且事先转换为float会产生{'x': 3.8999999999999999}错误,并且会大大浪费带宽.

Luk*_*sky 205

Simplejson 2.1及更高版本对Decimal类型具有原生支持:

>>> json.dumps(Decimal('3.9'), use_decimal=True)
'3.9'
Run Code Online (Sandbox Code Playgroud)

请注意,use_decimalTrue在默认情况下:

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):
Run Code Online (Sandbox Code Playgroud)

所以:

>>> json.dumps(Decimal('3.9'))
'3.9'
Run Code Online (Sandbox Code Playgroud)

希望此功能将包含在标准库中.

  • @MatthewSchinckel我认为不是.它实际上是一个字符串.如果你将结果字符串反馈给`json.loads(s,use_decimal = True),它会返回小数.整个过程中没有浮动.编辑上面的答案.希望原创海报很好用. (11认同)
  • 嗯,对我来说,这会将Decimal对象转换为浮点数,这是不可接受的.例如,使用货币时精度会下降. (7认同)
  • `simplejson.dumps(decimal.Decimal('2.2'))`也可以:没有显式的`use_decimal`(在simplejson/3.6.0上测试过).另一种加载它的方法是:`json.loads(s,parse_float = Decimal)`即,你可以使用stdlib`json`读取它(并且也支持旧的`simplejson`版本). (4认同)
  • 啊哈,我想我也没有在负载上使用 `use_decimal=True` 。 (2认同)
  • 对我来说 `json.dumps({'a' : Decimal('3.9')}, use_decimal=True)` 给出了 `'{"a": 3.9}'`。目标不是 `'{"a": "3.9"}'` 吗? (2认同)

Eli*_*ria 161

我想让大家知道我在运行Python 2.6.5的网络服务器上尝试了MichałMarczyk的答案,它运行良好.但是,我升级到Python 2.7并且它停止了工作.我试着想一些编码Decimal对象的方法,这就是我提出的:

import decimal

class DecimalEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, decimal.Decimal):
            return float(o)
        return super(DecimalEncoder, self).default(o)
Run Code Online (Sandbox Code Playgroud)

这应该有助于任何遇到Python 2.7问题的人.我测试了它,似乎工作正常.如果有人注意到我的解决方案中的任何错误或提出了更好的方法,请告诉我.

  • 使用`unicode`或`str`代替`float`来确保精度. (13认同)
  • Python 2.7改变了舍入浮点数的规则,因此这是有效的.请参阅http://stackoverflow.com/questions/1447287/format-floats-with-standard-json-module中的讨论 (4认同)
  • 对于我们这些不能使用simplejson(即在Google App Engine上)的人来说,这个答案是天赐之物. (2认同)
  • 54.3999中的问题......在Python 2.6.x中是很重要的,其中转换浮点数到字符串没有定期工作,但是将Decimal转换为str更加不正确,因为它将被序列化为带双引号"54.4的字符串" "`,而不是一个数字. (2认同)
  • 在python3中工作 (2认同)

Mic*_*zyk 132

子类化json.JSONEncoder怎么样?

class DecimalEncoder(json.JSONEncoder):
    def _iterencode(self, o, markers=None):
        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)._iterencode(o, markers)
Run Code Online (Sandbox Code Playgroud)

然后像这样使用它:

json.dumps({'x': decimal.Decimal('5.5')}, cls=DecimalEncoder)
Run Code Online (Sandbox Code Playgroud)

  • 此实现不再起作用.Elias Zamaria的一个是同样风格的人. (25认同)
  • 难道你不能只返回(str(o),)` `[o]`是一个只有1个元素的列表,为什么还要在它上面循环呢? (8认同)
  • 它给我带来了这个错误: TypeError: <generator object <genexpr> at 0x7fd42908da20> is not JSON Serialabilized (5认同)
  • @Mark:`return(str(o),)`将返回长度为1的元组,而答案中的代码返回长度为1的生成器.请参阅[iterencode()docs](http://docs.python.org/ 2 /库/ json.html#json.JSONEncoder.iterencode) (2认同)

ISO*_*MAn 34

在我的Flask应用程序中,使用python 2.7.11,flask alchemy(带有'db.decimal'类型)和Flask Marshmallow(用于'即时'序列化器和反序列化器),我有这个错误,每次我做GET或POST .序列化程序和反序列化程序无法将Decimal类型转换为任何JSON可识别格式.

我做了一个"pip install simplejson",然后加入

import simplejson as json
Run Code Online (Sandbox Code Playgroud)

序列化器和反序列化器再次开始呜呜声.我没有做任何其他事情...... DEciamls显示为'234.00'浮动格式.


tes*_*dal 28

我尝试从简单json切换到内置json用于GAE 2.7,并且存在小数问题.如果default返回str(o),则有引号(因为_iterencode在默认结果上调用_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]'
Run Code Online (Sandbox Code Playgroud)

  • 不幸的是,这在最近的Python 3中不起作用.现在有一些快速路径代码将所有浮动子类视为浮点数,并且不会完全调用它们的repr. (3认同)
  • 该解决方案自 v3.5.2rc1 起停止工作,请参阅 https://github.com/python/cpython/commit/e0805cf10ea84b44a13ad5649267edba7cb83ee9。有硬编码的 `float.__repr__`(失去精度),并且根本没有调用 `fakefloat.__repr__`。如果 fakefloat 有额外的方法 `def __float__(self): return self`,上面的解决方案对 python3 到 3.5.1 都可以正常工作。 (2认同)

Jav*_*zzi 21

缺少原生选项,所以我会为下一个寻找它的人/胆添加它.

从Django 1.7.x开始,有一个内置的DjangoJSONEncoder,你可以从中得到它django.core.serializers.json.

import json
from django.core.serializers.json import DjangoJSONEncoder
from django.forms.models import model_to_dict

model_instance = YourModel.object.first()
model_dict = model_to_dict(model_instance)

json.dumps(model_dict, cls=DjangoJSONEncoder)
Run Code Online (Sandbox Code Playgroud)

普雷斯托!

  • 你救了我的一天 (3认同)
  • @ std''OrgnlDave您是100%正确的。我忘记了我是怎么到达这里的,但是我在搜索词上附加了“ django”字样,然后在谷歌搜索了一下之后就找到了答案,并在这里添加了给像我这样的下一个人,它 (2认同)

ecp*_*ecp 12

对于那些不想使用第三方库的人...... Elias Zamaria 的答案的一个问题是它转换为浮点数,这可能会遇到问题。例如:

>>> json.dumps({'x': Decimal('0.0000001')}, cls=DecimalEncoder)
'{"x": 1e-07}'
>>> json.dumps({'x': Decimal('100000000000.01734')}, cls=DecimalEncoder)
'{"x": 100000000000.01733}'
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

这给了你:

>>> json.dumps({'x': Decimal('0.0000001')}, cls=DecimalEncoder)
'{"x": 0.0000001}'
>>> json.dumps({'x': Decimal('100000000000.01734')}, cls=DecimalEncoder)
'{"x": 100000000000.01734}'
Run Code Online (Sandbox Code Playgroud)


Anu*_*yal 11

3.9无法在IEEE浮点数中准确表示,它总是会出现3.8999999999999999,例如尝试 print repr(3.9),您可以在此处阅读更多相关内容:

http://en.wikipedia.org/wiki/Floating_point
http://docs.sun.com/source/806-3568/ncg_goldberg.html

因此,如果您不想浮动,只需要将其作为字符串发送,并允许将十进制对象自动转换为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)
Run Code Online (Sandbox Code Playgroud)


std*_*ave 11

我的$ .02!

我扩展了一堆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
Run Code Online (Sandbox Code Playgroud)

让我的生活变得更加轻松......

  • 这是不正确的:它会将3.9重现为""东西":"3.9"`. (3认同)
  • 你对此有引用吗?我读过的每个规范都暗示它依赖于实现. (2认同)

Nab*_*med 9

对于Django用户

最近遇到了TypeError: Decimal('2337.00') is not JSON serializable JSON编码,即json.dumps(data)

解决方案

# converts Decimal, Datetime, UUIDs to str for Encoding
from django.core.serializers.json import DjangoJSONEncoder  

json.dumps(response.data, cls=DjangoJSONEncoder)
Run Code Online (Sandbox Code Playgroud)

但是,现在Decimal值将是一个字符串,现在我们可以在解码数据时使用以下parse_float选项来显式设置十进制/浮点值解析器json.loads

import decimal 

data = json.loads(data, parse_float=decimal.Decimal) # default is float(num_str)
Run Code Online (Sandbox Code Playgroud)


Jam*_*Lin 6

这就是我从课堂上提取的内容

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)
Run Code Online (Sandbox Code Playgroud)

哪个通过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)
Run Code Online (Sandbox Code Playgroud)


Hug*_*ota 5

JSON标准文档(如json.org中链接):

JSON与数字的语义无关。在任何编程语言中,可以有各种容量和补码形式的数字类型,固定或浮动,二进制或十进制。这会使不同编程语言之间的交换变得困难。JSON而是仅提供人类使用的数字表示形式:数字序列。所有编程语言都知道如何理解数字序列,即使它们在内部表示形式上存在分歧。这足以允许互换。

因此,在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)
Run Code Online (Sandbox Code Playgroud)

然后在序列化数据时使用它:

json.dumps(data, cls=CustomJsonEncoder)
Run Code Online (Sandbox Code Playgroud)

从其他答案的注释中可以看出,较旧版本的python在转换为float时可能会弄乱表示形式,但现在不再如此。

要在Python中返回小数:

Decimal(str(value))
Run Code Online (Sandbox Code Playgroud)

Python 3.0文档中的小数点提示了该解决方案:

要从浮点数创建小数,请首先将其转换为字符串。

  • 这在 Python 3 中不是“固定的”。转换为 `float` * 必然* 会使您丢失十进制表示,并且 * 将 * 导致差异。如果使用“十进制”很重要,我认为最好使用字符串。 (2认同)