繁体   English   中英

Python JSON 序列化十进制 object

[英]Python JSON serialize a Decimal object

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

有没有好的方法来序列化这个? JSONDecoder 不接受 Decimal 对象,并且事先转换为浮点数会产生{'x': 3.8999999999999999}这是错误的,并且会浪费带宽。

Simplejson 2.1<\/a>及更高版本对 Decimal 类型具有原生支持:

>>> json.dumps(Decimal('3.9'), use_decimal=True)
'3.9'

我想让大家知道,我在运行 Python 2.6.5 的 Web 服务器上尝试了 Michał Marczyk 的答案,它运行良好。 但是,我升级到 Python 2.7 并且它停止工作。 我试图想出某种方式来编码 Decimal 对象,这就是我想出的:

import decimal

class DecimalEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, decimal.Decimal):
            return str(o)
        return super(DecimalEncoder, self).default(o)

json.JSONEncoder<\/code>怎么样?

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)

在我的 Flask 应用程序中,它使用 python 2.7.11、flask alchemy(带有“db.decimal”类型)和 Flask Marshmallow(用于“即时”序列化器和反序列化器),每次执行 GET 或 POST 时都会出现此错误. 序列化器和反序列化器未能将 Decimal 类型转换为任何 JSON 可识别格式。

我做了一个“pip install simplejson”,然后只需添加

import simplejson as json

缺少原生 Django 选项,所以我会为下一个寻找它的家伙\/胆小鬼添加它。<\/em>

从 Django 1.7.x 开始,有一个内置的DjangoJSONEncoder<\/code> ,您可以从django.core.serializers.json<\/code>获取它。

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)

我尝试从 simplejson 切换到 GAE 2.7 的内置 json,但小数点有问题。 如果默认返回 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]'

对于 Django 用户<\/strong>:

最近遇到TypeError: Decimal('2337.00') is not JSON serializable<\/code> while JSON encoding ie json.dumps(data)<\/code>

解决方案<\/strong>:

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

json.dumps(response.data, cls=DjangoJSONEncoder)

3.9<\/code>不能在 IEEE 浮点数中精确表示,它总是以3.8999999999999999<\/code>的形式出现,例如尝试print repr(3.9)<\/code> ,您可以在此处阅读更多信息:

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

因此,如果您不想要浮动,则只能选择将其作为字符串发送,并允许将十进制对象自动转换为 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)

我的 0.02 美元!

我扩展了一堆 JSON 编码器,因为我正在为我的 Web 服务器序列化大量数据。 这是一些不错的代码。 请注意,它可以轻松扩展到您喜欢的几乎任何数据格式,并将 3.9 复制为"thing": 3.9<\/code>

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

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

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

来自JSON 标准文档<\/a>,在json.org<\/a>中链接:

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

因此,在 JSON 中将小数表示为数字(而不是字符串)实际上是准确的。 贝娄是解决问题的可能方法。

定义自定义 JSON 编码器:

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

正如对其他答案的评论所指出的那样,旧版本的 python 在转换为浮点数时可能会弄乱表示,但现在情况不再如此。

要在 Python 中取回小数点:

这个解决方案在Python 3.0 的小数文档<\/a>中有所提示:

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

这就是我所拥有的,从我们的班级中提取的

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)

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

根据stdOrgnlDave 的<\/a>回答,我已经定义了这个包装器,它可以用可选的种类调用,所以编码器只适用于您项目中的某些种类。 我相信这项工作应该在你的代码中完成,而不是使用这个“默认”编码器,因为“它比隐式更好”,但我知道使用它可以节省一些时间。 :-)

import time
import json
import decimal
from uuid import UUID
from datetime import datetime

def JSONEncoder_newdefault(kind=['uuid', 'datetime', 'time', 'decimal']):
    '''
    JSON Encoder newdfeault is a wrapper capable of encoding several kinds
    Use it anywhere on your code to make the full system to work with this defaults:
        JSONEncoder_newdefault()  # for everything
        JSONEncoder_newdefault(['decimal'])  # only for Decimal
    '''
    JSONEncoder_olddefault = json.JSONEncoder.default

    def JSONEncoder_wrapped(self, o):
        '''
        json.JSONEncoder.default = JSONEncoder_newdefault
        '''
        if ('uuid' in kind) and isinstance(o, uuid.UUID):
            return str(o)
        if ('datetime' in kind) and isinstance(o, datetime):
            return str(o)
        if ('time' in kind) and isinstance(o, time.struct_time):
            return datetime.fromtimestamp(time.mktime(o))
        if ('decimal' in kind) and isinstance(o, decimal.Decimal):
            return str(o)
        return JSONEncoder_olddefault(self, o)
    json.JSONEncoder.default = JSONEncoder_wrapped

# Example
if __name__ == '__main__':
    JSONEncoder_newdefault()

我有一个Decimal('3.9')作为对象的一部分,并希望将其编码为一个看起来像{'x': 3.9}的JSON字符串。 我不在乎客户端的精度,因此浮点数很好。

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

如果有人仍在寻找答案,很可能是您尝试编码的数据中有一个“NaN”。 因为 NaN 被 Python 视为浮点数。

"

如果您想将包含小数的字典传递给requests<\/code>库(使用json<\/code>关键字参数),您只需安装simplejson<\/code> :

$ 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')})

对于任何想要快速解决方案的人来说,我是如何从 Django 中的查询中删除 Decimal

total_development_cost_var = process_assumption_objects.values('total_development_cost').aggregate(sum_dev = Sum('total_development_cost', output_field=FloatField()))
total_development_cost_var = list(total_development_cost_var.values())
  • 第 1 步:在您的查询中使用 , output_field=FloatField()
  • 第 2 步:使用列表,例如 list(total_development_cost_var.values())

希望能帮助到你

这个问题很老,但是对于大多数用例来说,Python3 中似乎有一个更好、更简单的解决方案:

number = Decimal(0.55)
converted_number = float(number) # Returns: 0.55 (as type float)

如果您确定 Decimal 是您的 json 转储方法中唯一的坏人,我的 2 美分即可轻松解决:

print(json.loads(json.dumps({
    'a': Decimal(1230),
    'b': Decimal(11111111123.22),
}, default=lambda x: eval(str(x)))))

>>> {'a': 1230, 'b': 11111111123.22}

这里的“聪明”事情是使用默认值将 Decimal 自动转换为 int 或 float,利用 eval function: default=lambda x: eval(str(x))

但是在你的代码上使用 eval 时要小心,因为它会导致安全问题;)

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

  • 由于精度问题而float
  • str由于 openapi 限制

我们仍然需要直接十进制到数字 json 序列化。

这是我们对@tesdal 的 fakefloat 解决方案的扩展(在 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开发者为什么要强制我们在不适合的地方使用float。

我将与 flask 2.1.0 分享对我有用的方法当我创建必须从 jsonify 使用的字典时,我使用了舍入:

json_dict['price'] = round(self.price, ndigits=2) if self.price else 0

所以这样我就可以在不使用某些全局配置的情况下返回 D.DD 编号或 0。 这很好,因为某些 Decimals 必须更大,例如纬度和经度坐标。

return jsonify(json_dict)

这可以通过添加来完成

    elif isinstance(o, decimal.Decimal):
        yield str(o)

\\Lib\\json\\encoder.py:JSONEncoder._iterencode ,但我希望有更好的解决方案

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM