Python JSON serializar un objeto decimal
Tengo un Decimal('3.9')
como parte de un objeto, y deseo codificar esto en una cadena JSON que debería parecerse a {'x': 3.9}
. No me importa la precisión en el lado del cliente, así que un flotador está bien.
¿Hay una buena manera de serializar esto? JSONDecoder no acepta objetos decimales, y convertir a un float de antemano produce {'x': 3.8999999999999999}
lo cual es incorrecto, y será un gran desperdicio de ancho de banda.
14 answers
¿Qué tal la subclase 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)
Entonces úsalo así:
json.dumps({'x': decimal.Decimal('5.5')}, cls=DecimalEncoder)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-25 07:14:05
Simplejson 2.1 y superior tiene soporte nativo para el tipo decimal:
>>> json.dumps(Decimal('3.9'), use_decimal=True)
'3.9'
Tenga en cuenta que use_decimal
es True
por defecto:
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):
Así que:
>>> json.dumps(Decimal('3.9'))
'3.9'
Con suerte, esta característica se incluirá en la biblioteca estándar.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-23 16:36:38
Me gustaría que todos sepan que probé la respuesta de Michał Marczyk en mi servidor web que ejecutaba Python 2.6.5 y funcionó bien. Sin embargo, actualicé a Python 2.7 y dejó de funcionar. Traté de pensar en algún tipo de forma de codificar objetos decimales y esto es lo que se me ocurrió:
class DecimalEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, decimal.Decimal):
return float(o)
return super(DecimalEncoder, self).default(o)
Esto debería ayudar a cualquiera que tenga problemas con Python 2.7. Lo probé y parece que funciona bien. Si alguien nota algún error en mi solución o se le ocurre una mejor way, por favor házmelo saber.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-07 19:29:03
Intenté cambiar de simplejson a builtin json para GAE 2.7, y tuve problemas con el decimal. Si el valor predeterminado devuelve str (o) había comillas (porque _iterencode llama a _iterencode en los resultados del valor predeterminado), y float(o) eliminaría el 0 final.
Si por defecto devuelve un objeto de una clase que hereda de float (o cualquier cosa que llame a repr sin formato adicional) y tiene un método __repr__ personalizado, parece funcionar como yo quiero.
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]'
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-25 21:17:24
En mi aplicación Flask, Que usa python 2.7.11, flask alchemy(con 'db.decimal' types), y Flask Marshmallow (para' instant ' serializer y deserializer), tuve este error, cada vez que hice un GET o POST. El serializador y el deserializador no pudieron convertir los tipos decimales a ningún formato identificable con JSON.
Hice un "pip install simplejson", entonces Simplemente añadiendo
import simplejson as json
El serializador y el deserializador comienzan a ronronear de nuevo. No hice nada más... Los DECIAML se muestran como formato flotante '234.00'.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-31 19:21:54
3.9
no se puede representar exactamente en los flotadores IEEE, siempre vendrá como 3.8999999999999999
, por ejemplo, try print repr(3.9)
, puede leer más al respecto aquí:
Http://en.wikipedia.org/wiki/Floating_point
http://docs.sun.com/source/806-3568/ncg_goldberg.html
Así que si no quieres float, la única opción que tienes es enviarla como string, y para permitir la conversión automática de objetos decimales a JSON, haz algo como esto:
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)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-28 16:39:05
Mi $.02!
Extiendo un montón del codificador JSON ya que estoy serializando toneladas de datos para mi servidor web. Aquí tienes un buen código. Tenga en cuenta que es fácilmente extensible a casi cualquier formato de datos que desee y reproducirá 3.9 como "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
Hace mi vida mucho más fácil...
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-27 05:35:13
Esto es lo que tengo, extraído de nuestra clase
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)
Que pasa 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)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-02 01:30:54
Falta la opción nativa, así que la agregaré para el próximo chico/gall que la busque.
A partir de Django 1.7.x hay un DjangoJSONEncoder
incorporado que puedes obtener de 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)
Presto!
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-22 09:09:01
Basado en stdOrgnlDave respuesta He definido esta envoltura que se puede llamar con tipos opcionales para que el codificador funcione solo para ciertos tipos dentro de sus proyectos. Creo que el trabajo debe hacerse dentro de su código y no usar este codificador "predeterminado" ya que "es mejor explícito que implícito", pero entiendo que usar esto le ahorrará algo de tiempo. :-)
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()
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-17 11:12:42
Del Documento Estándar JSON , como se enlaza en json.org :
JSON es agnóstico sobre la semántica de los números. En cualquier lenguaje de programación, puede haber una variedad de tipos numéricos de diversas capacidades y complementos, fijos o flotantes, binarios o decimales. Que puede hacer el intercambio entre diferentes lenguajes de programación es difícil. JSON en su lugar ofrece solo la representación de números que los humanos usan: una secuencia de dígitos. Todos los lenguajes de programación saber cómo dar sentido a digit secuencias incluso si no están de acuerdo en las representaciones internas. Eso es suficiente para permitir el intercambio.
Así que en realidad es preciso representar decimales como números (en lugar de cadenas) en JSON. A continuación se encuentra una posible solución al problema.
Defina un codificador JSON personalizado:
import json
class CustomJsonEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, Decimal):
return float(obj)
return super(CustomJsonEncoder, self).default(obj)
Luego utilícelo al serializar sus datos:
json.dumps(data, cls=CustomJsonEncoder)
Como se observa en los comentarios sobre las otras respuestas, las versiones anteriores de python podrían estropear la representación al convertir a float, pero ese ya no es el caso.
Para obtener ese decimal exacto en Python:
Decimal(str(value))
Esta solución se insinúa en la documentación de Python 3.0 sobre decimales :
Para crear un Decimal a partir de un flotador, primero conviértalo en una cadena.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-08 01:51:04
Si desea pasar un diccionario que contenga decimales a la biblioteca requests
(utilizando el argumento de la palabra clave json
), simplemente necesita instalar 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')})
La razón del problema es que requests
usa simplejson
solo si está presente, y vuelve a usar el incorporado json
si no está instalado.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-14 19:22:55
Puede crear un codificador JSON personalizado según sus requisitos.
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)
El Decodificador se puede llamar así,
import json
from decimal import Decimal
json.dumps({'x': Decimal('3.9')}, cls=CustomJSONEncoder)
Y la salida será:
>>'{"x": 3.9}'
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-06 11:47:09
Esto se puede hacer añadiendo
elif isinstance(o, decimal.Decimal):
yield str(o)
En \Lib\json\encoder.py:JSONEncoder._iterencode
, pero esperaba una mejor solución
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-25 05:37:41