Python JSON 序列化 Decimal 对象

2024-12-19 09:23:00
admin
原创
80
摘要:问题描述:我有一个Decimal('3.9')对象的一部分,并希望将其编码为 JSON 字符串,该字符串应类似于{'x': 3.9}。我不关心客户端的精度,因此浮点数就可以了。有没有好的方法来序列化它? JSONDecoder 不接受 Decimal 对象,并且事先转换为浮点数{'x': 3.89999999...

问题描述:

我有一个Decimal('3.9')对象的一部分,并希望将其编码为 JSON 字符串,该字符串应类似于{'x': 3.9}。我不关心客户端的精度,因此浮点数就可以了。

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


解决方案 1:

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

>>> import simplejson as json

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

所以:

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

希望该功能能够被纳入标准库中。

解决方案 2:

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

import json
import decimal

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

请注意,这会将小数转换为其字符串表示形式(例如;"1.2300")以 a. 不丢失有效数字和 b. 防止舍入误差。

这应该可以帮助那些在使用 Python 2.7 时遇到问题的人。我测试过它,它似乎工作正常。如果有人发现我的解决方案中有任何错误或想出更好的方法,请告诉我。

使用示例:

json.dumps({'x': decimal.Decimal('5.5')}, cls=DecimalEncoder)

# returns: '{"x": "5.5"}'

解决方案 3:

那么子类化怎么样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)

解决方案 4:

缺少本机 Django 选项,因此我将为下一个寻找它的人/胆量添加它。

DjangoJSONEncoder从 Django 1.7.x 开始,您可以从中获取一个内置功能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)

瞧!

解决方案 5:

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

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

import simplejson as json

序列化器和反序列化器又开始发出呼噜声。我什么也没做……DEciamls 显示为“234.00”浮点格式。

解决方案 6:

我尝试将 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")

print(json.dumps([10.20, "10.20", Decimal('0.299999999999999988897769753748434595763683319091796875')], default=defaultencode))
# Should print:
# [10.2, "10.20", 0.299999999999999988897769753748434595763683319091796875]

解决方案 7:

对于 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)

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

import decimal 

data = json.loads(data, parse_float=decimal.Decimal) # default is float(num_str)

解决方案 8:

对于那些不想使用第三方库的人来说...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 兼容的类型(如浮点数),然后以正常方式进行编码。 的问题encode()是它(通常)仅在顶层起作用。但它仍然可用,只需做一些额外的工作(python 3.x):

import json
from collections.abc import Mapping, Iterable
from decimal import Decimal, Context, MAX_PREC

_context = Context(prec=MAX_PREC)  # optional, to handle more than the default 28 digits, if necessary

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):
            # (the _context is optional, for handling more than 28 digits)
            return f'{obj.normalize(_context):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}'
>>> json.dumps({'x': Decimal('123450.1000000000000000000000000000001000')}, cls=DecimalEncoder)
'{"x": 123450.1000000000000000000000000000001}'

正如 Nickolay 所指出的,如果您使用的有效数字超过 28 位,则可能会遇到 Decimal 默认精度的问题。因此,上述(修改后的)代码包含 using ContextMAX_PREC这实际上不会对性能产生影响,因此即使您不期望那么多数字,也可以使用这样的自定义上下文。

解决方案 9:

我的 $.02!

由于我正在为我的网络服务器序列化大量数据,因此我扩展了许多 JSON 编码器。以下是一些不错的代码。请注意,它可以轻松扩展到几乎任何你喜欢的数据格式,并将重现 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

让我的生活变得轻松多了……

解决方案 10:

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)

解决方案 11:

来自JSON 标准文档,链接在json.org中:

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

因此,在 JSON 中将 Decimals 表示为数字(而不是字符串)实际上是准确的。下面是该问题的一种可能的解决方案。

定义自定义 JSON 编码器:

import json


class CustomJsonEncoder(json.JSONEncoder):

    def default(self, obj):
        if isinstance(obj, Decimal):
            return float(obj)
        return super(CustomJsonEncoder, self).default(obj)

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

json.dumps(data, cls=CustomJsonEncoder)

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

在 Python 中取回小数:

Decimal(str(value))

Python 3.0 关于小数的文档中暗示了这个解决方案:

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

解决方案 12:

这是我从我们班摘录的

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)

通过单元测试:

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)

解决方案 13:

根据stdOrgnlDave 的回答,我定义了这个包装器,可以使用可选类型调用它,因此编码器将仅适用于项目内的某些类型。我相信这项工作应该在你的代码中完成,而不是使用这个“默认”编码器,因为“显式比隐式更好”,但我知道使用它会节省你的一些时间。:-)

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

解决方案 14:

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

解决方案 15:

如果您想将包含小数的字典传递给requests库(使用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')})

问题的原因在于只有存在时才requests使用,如果没有安装simplejson则返回到内置。json

解决方案 16:

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

解决方案 17:

无论出于什么原因,我的要求是省略不必要的 0,因此 eg123.0应该变成123。此外,我的要求是将 json 输出为数字而不是字符串。

因此,感谢 Elias Zamarias 的解决方案,我得出了以下结论:

class DecimalEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, decimal.Decimal):
            if float(o) == int(o):
                return int(o)
            else:
                return float(o)
        return super(DecimalEncoder, self).default(o)

解决方案 18:

您可以使用simplejsonujson

>>> import ujson
>>> import decimal
>>> ujson.dumps(decimal.Decimal(1.23))
'1.23'
>>> ujson.dumps({"w":decimal.Decimal(1.23)}) 
'{"w":1.23}'
>>>

请注意,ujson 比 json 更快
https://dev.to/dollardhingra/benchmarking-python-json-serializers-json-vs-ujson-vs-orjson-1o16

解决方案 19:

对于任何想要快速解决方案的人,这里是我如何从 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 步:使用列表,例如列表(total_development_cost_var.values())

希望有帮助

解决方案 20:

似乎这里有足够的答案,但我来这里需要一种方法将 numpy 数字列表转换为四舍五入的小数,以便导出simplejson

def encode_floats(nums, decimals=3):
    """
    Convert numpy numbers to ones ready for dumping with simplejson
    useful when you don't want your json exports to be bloated with too many
    significant figures
    :param nums: list of numbers
    :param decimals: number of decimals you want to output
    """
    def convert_to_rounded_dec(num):
        # convert to decimal
        num_dec = Decimal(np.float64(num))
        # create decimal number for quantizing
        round_dec = Decimal("".join(['0.'] + ['0'] * (decimals - 1) + ['1']))
        # actually round the number here
        num_round = num_dec.quantize(round_dec, rounding='ROUND_HALF_UP')
        return num_round
    # apply the function to the list of numbers
    func = (np.vectorize(convert_to_rounded_dec))
    # remove the numpy data type by converting to a list
    return func(nums).tolist()

解决方案 21:

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

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

您可以直接转换Decimalfloat

解决方案 22:

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

解决方案 23:

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

  • 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 开发人员强迫我们在不合适的地方使用浮点数。

解决方案 24:

我的解决方案:---

import json
from decimal import *

num = Decimal('8439.67')


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


json_str = json.dumps({'number': num}, cls=DecimalEncoder)
print(json_str)
print(type(json_str))

'''
Output:-
{"number": "8439.67"}
<class 'str'>
'''
相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1008  
  在项目管理中,变更是一个不可避免的现象。无论是客户需求的调整、市场环境的变化,还是技术方案的更新,都可能引发项目的变更。如果处理不当,这些变更可能会导致项目延期、成本超支,甚至项目失败。因此,如何有效地应对项目变更,成为项目管理中的核心挑战之一。IPD(集成产品开发)作为一种高效的项目管理方法,其流程图不仅能够帮助团队...
IPD流程中的charter   0  
  IPD(Integrated Product Development,集成产品开发)是华为在长期实践中总结出的一套高效产品开发管理体系。它不仅帮助华为在全球市场中脱颖而出,也成为许多企业提升产品开发效率的参考标杆。IPD的核心在于通过跨部门协作、流程优化和资源整合,实现从需求分析到产品交付的全生命周期管理。通过实施IP...
IPD开发流程管理   0  
  华为IPD(集成产品开发)流程是一种以客户需求为导向、跨部门协同的高效项目管理方法。它通过系统化的流程设计和严格的阶段控制,确保项目从概念到交付的每个环节都能高效运作。IPD流程的核心在于打破传统职能部门的壁垒,将产品开发、市场、销售、供应链等关键环节整合到一个统一的框架中,从而实现资源的优化配置和信息的无缝流动。这种...
IPD流程中TR   0  
  在项目管理的实践中,CDCP(Certified Data Center Professional)认证评审是一个至关重要的环节。通过这一评审,项目团队不仅能够验证其数据中心设计和运营的合规性,还能提升整体管理水平。为了确保评审的顺利进行,准备一系列关键文档是必不可少的。这些文档不仅是评审的依据,也是项目团队与评审专家...
华为IPD是什么   0  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用