Python JSON сериализует десятичный объект

У меня есть Decimal('3.9') как часть объекта, и я хочу кодировать его в строку JSON, которая должна выглядеть как {'x': 3.9}. Я не забочусь о точности на стороне клиента, поэтому поплавок в порядке.

Есть ли хороший способ сериализовать это? JSONDecoder не принимает десятичные объекты, а преобразование в поплавок заранее дает {'x': 3.8999999999999999}, что неверно, и будет большой тратой полосы пропускания.

Ответ 1

Как насчет подкласса 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)

Ответ 2

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'

Надеюсь, эта функция будет включена в стандартную библиотеку.

Ответ 3

Я хотел бы, чтобы все знали, что я попробовал ответить Михаила Марчика на своем веб-сервере, на котором работал Python 2.6.5, и он работал нормально. Тем не менее, я обновился до Python 2.7, и он перестал работать. Я попытался придумать какой-нибудь способ кодирования десятичных объектов, и вот что я придумал:

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. Я проверил это, и, кажется, работает нормально. Если кто-то заметит какие-либо ошибки в моем решении или найдет лучший способ, пожалуйста, дайте мне знать.

Ответ 4

В моем приложении Flask, в котором используется python 2.7.11, алхимия фляж (с типами "db.decimal" ) и Flask Marshmallow (для "мгновенного" сериализатора и десериализатора), я имел эту ошибку каждый раз, когда я делал GET или POST. Сериализатор и десериализатор не смогли преобразовать десятичные типы в любой распознаваемый JSON формат.

Я сделал "pip install simplejson", затем Просто добавив

import simplejson as json

сериализатор и десериализатор снова начинают мурлыкать. Я больше ничего не делал... DEciamls отображаются как плавающий формат 234.00.

Ответ 5

Я попытался переключиться с simplejson на встроенный json для GAE 2.7 и имел проблемы с десятичной точкой. Если по умолчанию возвращена str (o), там были кавычки (потому что _iterencode вызывает _iterencode по результатам по умолчанию), а float (o) удаляет завершающий 0.

Если по умолчанию возвращается объект класса, который наследует от float (или все, что вызывает повтор без дополнительного форматирования) и имеет собственный метод __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]'

Ответ 6

Собственный вариант отсутствует, поэтому я добавлю его для следующего парня/желчи, который его ищет.

Начиная с 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)

Presto!

Ответ 7

3.9 не может быть точно представлен в поплавках IEEE, он всегда будет 3.8999999999999999 как 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)

Ответ 8

Мои $.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

Делает мою жизнь намного проще...

Ответ 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)

Но теперь десятичное значение будет строкой, теперь мы можем явно установить анализатор десятичных/плавающих значений при декодировании данных, используя опцию parse_float в json.loads:

import decimal 

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

Ответ 10

Это то, что я получил, извлеченный из нашего класса

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)

Ответ 11

Из стандартного документа 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 с десятичными знаками:

Чтобы создать десятичное число из числа с плавающей запятой, сначала преобразуйте его в строку.

Ответ 12

Основываясь на ответе 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()

Ответ 13

Вы можете создать собственный кодер 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}'

Ответ 14

Если вы хотите передать словарь, содержащий десятичные знаки, в библиотеку 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 если он не установлен.

Ответ 15

это можно сделать, добавив

    elif isinstance(o, decimal.Decimal):
        yield str(o)

в \Lib\json\encoder.py:JSONEncoder._iterencode, но я надеялся на лучшее решение