我有一个Decimal('3.9')
对象的一部分,希望将其编码为JSON字符串,看起来像{'x': 3.9}
。我不在乎客户端的精度,因此浮点数很好。
是否有序列化此序列的好方法?JSONDecoder不接受Decimal对象,并且事先转换为float会产生{'x': 3.8999999999999999}
错误,这将浪费大量带宽。
我有一个Decimal('3.9')
对象的一部分,希望将其编码为JSON字符串,看起来像{'x': 3.9}
。我不在乎客户端的精度,因此浮点数很好。
是否有序列化此序列的好方法?JSONDecoder不接受Decimal对象,并且事先转换为float会产生{'x': 3.8999999999999999}
错误,这将浪费大量带宽。
Answers:
子类化如何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)
然后像这样使用它:
json.dumps({'x': decimal.Decimal('5.5')}, cls=DecimalEncoder)
DecimalEncoder()._iterencode(decimal.Decimal('3.9')).next()
返回了正确的对象'3.9'
,但是DecimalEncoder()._iterencode(3.9).next()
返回了一个生成器对象,该对象仅'3.899...'
在您堆放在另一个对象上时才会返回.next()
。发电机有趣的业务。哦,好了,应该现在就工作。
return (str(o),)
代替吗?[o]
是只有1个元素的列表,为什么要遍历它呢?
return (str(o),)
将返回长度为1的元组,而答案中的代码将返回长度为1的生成器。请参阅iterencode()文档
Simplejson 2.1及更高版本具有对Decimal类型的本地支持:
>>> 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'
希望此功能将包含在标准库中。
json.loads(s, use_decimal=True)
它,则返回十进制数。在整个过程中没有漂浮。编辑以上答案。希望原始海报可以使用。
use_decimal=True
负载。
json.dumps({'a' : Decimal('3.9')}, use_decimal=True)
给'{"a": 3.9}'
。目标不是'{"a": "3.9"}'
吗?
simplejson.dumps(decimal.Decimal('2.2'))
也可以:无显式use_decimal
(在simplejson / 3.6.0上测试)。加载回去的另一种方法是:json.loads(s, parse_float=Decimal)
即,您可以使用stdlib读取它json
(simplejson
也支持旧版本)。
我想让大家知道,我在运行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 float(o)
return super(DecimalEncoder, self).default(o)
希望这应该对任何遇到Python 2.7问题的人有所帮助。我测试了它,看来效果很好。如果有人注意到我的解决方案中的任何错误或提出了更好的方法,请告诉我。
unicode
或str
代替float
以确保精度。
"54.4"
,而不是一个号码。
在我的Flask应用程序中,它使用python 2.7.11,flask alchemy(具有“ db.decimal”类型)和Flask Marshmallow(用于“即时”序列化器和反序列化器),每次执行GET或POST时,我都会遇到此错误。序列化器和反序列化器无法将Decimal类型转换为任何JSON可识别格式。
我做了一个“ pip install simplejson”,然后只需添加
import simplejson as json
序列化器和解串器再次开始发出嗡嗡声。我什么也没做... DEciamls显示为'234.00'浮点格式。
Decimal('0.00') is not JSON serializable
通过pip安装它后仍然可以解决。当您同时使用棉花糖和石墨烯时,就是这种情况。当在rest api上调用查询时,棉花糖可用于十进制字段。但是,当用graphql调用它时,会引发is not JSON serializable
错误。
我尝试从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")
json.dumps([10.20, "10.20", Decimal('10.20')], default=defaultencode)
'[10.2, "10.20", 10.20]'
float.__repr__
硬编码(会降低精度),fakefloat.__repr__
根本不会被调用。如果fakefloat具有其他方法,则上述解决方案对于python3到3.5.1均适用def __float__(self): return self
。
缺少本机选项,因此我将其添加到寻找它的下一个家伙/画廊。
从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)
快点!
我的$ .02!
因为要为Web服务器序列化大量数据,所以我扩展了许多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
让我的生活变得更加轻松...
"thing": "3.9"
。
3.9
不能在IEEE浮点数中精确表示,它总是以表示3.8999999999999999
,例如try 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)
json.loads("3.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)
但是,现在Decimal值将是一个字符串,现在我们可以在解码数据时使用parse_float
in中的option 显式设置十进制/浮点值解析器json.loads
:
import decimal
data = json.loads(data, parse_float=decimal.Decimal) # default is float(num_str)
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)
然后在序列化数据时使用它:
json.dumps(data, cls=CustomJsonEncoder)
从其他答案的注释中可以看出,较旧版本的python在转换为float时可能会弄乱表示形式,但现在不再如此。
要在Python中返回小数:
Decimal(str(value))
Python 3.0文档中的小数点提示了该解决方案:
要从浮点数创建小数,请首先将其转换为字符串。
float
必然会丢失十进制表示,并会导致差异。如果Decimal
很重要,我认为最好使用字符串。
这就是我从课堂上摘录的
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)
json.loads(myString, cls=CommonJSONEncoder)
评论应该是json.loads(myString, cls=CommonJSONDecoder)
您可以根据需要创建自定义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}'
对于那些不想使用第三方库的人来说……Elias Zamaria的问题是它会转换为float,这会遇到问题。例如:
>>> 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兼容类型(例如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)
这给你:
>>> json.dumps({'x': Decimal('0.0000001')}, cls=DecimalEncoder)
'{"x": 0.0000001}'
>>> json.dumps({'x': Decimal('100000000000.01734')}, cls=DecimalEncoder)
'{"x": 100000000000.01734}'
根据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()
如果要将包含小数的字典传递给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
未安装时退回到内置。
这可以通过添加
elif isinstance(o, decimal.Decimal):
yield str(o)
在\Lib\json\encoder.py:JSONEncoder._iterencode
,但我希望有一个更好的解决方案