Python JSON 序列化 Decimal 对象
- 2024-12-19 09:23:00
- admin 原创
- 79
问题描述:
我有一个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 Context
。MAX_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:
您可以使用simplejson
或ujson
>>> 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)
您可以直接转换Decimal
为float
。
解决方案 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'>
'''
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理必备:盘点2024年13款好用的项目管理软件