Programing

Python JSON은 Decimal 객체를 직렬화합니다.

lottogame 2020. 5. 8. 08:13
반응형

Python JSON은 Decimal 객체를 직렬화합니다.


나는 Decimal('3.9')객체의 일부를 가지고 있으며 이것을 JSON 문자열로 인코딩하고 싶습니다 {'x': 3.9}. 나는 클라이언트 측의 정밀도에 관심이 없으므로 플로트가 좋습니다.

이것을 직렬화하는 좋은 방법이 있습니까? JSONDecoder는 Decimal 객체를 허용하지 않으며 사전에 float로 변환하면 {'x': 3.8999999999999999}잘못된 결과를 낳으며 대역폭을 크게 낭비합니다.


서브 클래 싱은 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 이상은 기본적으로 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'

이 기능이 표준 라이브러리에 포함되기를 바랍니다.


Python 2.6.5를 실행하는 웹 서버에서 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)

파이썬 2.7에 문제가있는 사람이라면 누구나 도움이 될 것입니다. 나는 그것을 테스트했고 잘 작동하는 것 같다. 누군가 내 솔루션의 버그를 발견하거나 더 나은 방법을 찾은 경우 알려주십시오.


Python 2.7.11, 플라스크 연금술 ( 'db.decimal'유형) 및 Flask Marshmallow ( 'instant'serializer 및 deserializer의 경우)를 사용하는 Flask 앱에서 GET 또는 POST를 수행 할 때 마다이 오류가 발생했습니다. . serializer 및 deserializer가 Decimal 형식을 JSON 식별 가능한 형식으로 변환하지 못했습니다.

"pip install simplejson"을 한 다음

import simplejson as json

시리얼 라이저와 디시리얼라이저가 다시 시작됩니다. 나는 아무것도하지 않았다 ... DEciamls는 '234.00'플로트 형식으로 표시됩니다.


GAE 2.7에서 simplejson에서 내장 json으로 전환하려고 시도했으며 소수에 문제가 있습니다. default가 str (o)를 반환하면 _iterencode가 default 결과로 _iterencode를 호출하기 때문에 따옴표가 있었고 float (o)는 후행 0을 제거합니다.

기본이 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]'

기본 옵션이 누락되었으므로 다음 사람 / 공을 위해 추가 할 것입니다.

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)

프레스토 악장!


3.9IEEE float로 정확하게 표현할 수는 없습니다. 항상으로 나타납니다 3.8999999999999999. 예 : try 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.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

내 인생을 훨씬 더 쉽게 만듭니다 ...


Django 사용자의 경우 :

최근에 TypeError: Decimal('2337.00') is not JSON serializableJSON 인코딩 하는 동안 왔습니다.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옵션을 사용하여 데이터를 디코딩 할 때 10 진수 / 부동 값 파서를 명시 적으로 설정할 수 있습니다 json.loads.

import decimal 

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

이것은 우리 수업에서 추출한 것입니다.

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 표준 문서 에 링크로 json.org :

JSON is agnostic about the semantics of numbers. In any programming language, there can be a variety of number types of various capacities and complements, fixed or floating, binary or decimal. That can make interchange between different programming languages difficult. JSON instead offers only the representation of numbers that humans use: a sequence of digits. All programming languages know how to make sense of digit sequences even if they disagree on internal representations. That is enough to allow interchange.

So it's actually accurate to represent Decimals as numbers (rather than strings) in JSON. Bellow lies a possible solution to the problem.

Define a custom JSON encoder:

import json


class CustomJsonEncoder(json.JSONEncoder):

    def default(self, obj):
        if isinstance(obj, Decimal):
            return float(obj)
        return super(CustomJsonEncoder, self).default(obj)

Then use it when serializing your data:

json.dumps(data, cls=CustomJsonEncoder)

As noted from comments on the other answers, older versions of python might mess up the representation when converting to float, but that's not the case anymore.

To get the decimal back in Python:

Decimal(str(value))

This solution is hinted in Python 3.0 documentation on decimals:

To create a Decimal from a float, first convert it to a string.


Based on stdOrgnlDave answer I have defined this wrapper that it can be called with optional kinds so the encoder will work only for certain kinds inside your projects. I believe the work should be done inside your code and not to use this "default" encoder since "it is better explicit than implicit", but I understand using this will save some of your time. :-)

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()

You can create a custom JSON encoder as per your requirement.

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)

The Decoder can be called like this,

import json
from decimal import Decimal
json.dumps({'x': Decimal('3.9')}, cls=CustomJSONEncoder)

and the output will be:

>>'{"x": 3.9}'

If you want to pass a dictionary containing decimals to the requests library (using the json keyword argument), you simply need to install 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')})

The reason of the problem is that requests uses simplejson only if it is present, and falls back to the built-in json if it is not installed.


this can be done by adding

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

in \Lib\json\encoder.py:JSONEncoder._iterencode, but I was hoping for a better solution

참고URL : https://stackoverflow.com/questions/1960516/python-json-serialize-a-decimal-object

반응형