Python JSON сериализует десятичный объект
у меня есть Decimal('3.9') как часть объекта, и хотите закодировать это в строку JSON, которая должна выглядеть как {'x': 3.9}. Я не забочусь о точности на стороне клиента, так что поплавок в порядке.
есть ли хороший способ сериализовать это? JSONDecoder не принимает десятичные объекты, и преобразование в float заранее дает {'x': 3.8999999999999999} что неправильно, и будет большой тратой пропускной способности.
14 ответов:
как насчет подклассы
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)
Simplejson 2.1 и выше, имеет встроенную поддержку для десятичного типа:
>>> 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'Надеюсь, эта функция будет включена в стандартную библиотеку.
Я хотел бы, чтобы все знали, что я попробовал ответ Михаила Марчика на моем веб-сервере, на котором был запущен Python 2.6.5, и он работал нормально. Однако я обновился до Python 2.7, и он перестал работать. Я попытался придумать какой-то способ кодирования десятичных объектов, и вот что я придумал:
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. Я проверил его, и он, кажется, работает нормально. Если кто-нибудь заметит какие-либо ошибки в моем решении или придумает с лучшим способом, пожалуйста, дайте мне знать.
Я попытался переключиться с simplejson на встроенный json для GAE 2.7 и имел проблемы с десятичным числом. Если возвращается значение по умолчанию стр(о) там были кавычки (потому что _iterencode звонки _iterencode по результатам умолчанию), и плавать(О) бы удалить трейлинг-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]'
в моей колбе приложение, которое использует python 2.7.11, колба алхимия(с ' db.десятичные "типы) и колба зефир ( для "мгновенного" сериализатора и десериализатора), у меня была эта ошибка, каждый раз, когда я делал GET или POST. Сериализатор и десериализатор не смогли преобразовать десятичные типы в любой идентифицируемый формат JSON.
Я сделал "pip install simplejson", затем Просто добавив
import simplejson as jsonсериализатор и десериализатор снова начинает мурлыкать. Больше я ничего не делал... DEciamls отображаются в формате' 234.00 ' с плавающей точкой.
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так что если вы не хотите float, только вариант вы должны отправить его в виде строки, и чтобы разрешить автоматическое преобразование десятичных объектов в 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)
мои $.02!
я расширяю кучу кодировщика JSON, так как я сериализую тонны данных для своего веб-сервера. Вот хороший код. Обратите внимание, что он легко расширяется практически до любого формата данных, который вам нравится, и будет воспроизводить 3.9 как
"thing": 3.9JSONEncoder_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делает мою жизнь намного легче...
это то, что у меня есть, извлеченные из нашего класса
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), который проходит 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)
родной вариант отсутствует, поэтому я добавлю его для следующего парня/желчи, который его ищет.
начиная с 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)вуаля!
на основе 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()
С стандартный документ JSON, как связано в json.org:
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, сначала преобразуйте его в строку.
если вы хотите передать словарь, содержащий дроби к
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если он не установлен.
вы можете создать пользовательский кодер 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}'
Это можно сделать, добавив
elif isinstance(o, decimal.Decimal): yield str(o)на
\Lib\json\encoder.py:JSONEncoder._iterencode, но я надеялся на лучшее решение
Comments