Python JSON serializuje obiekt dziesiętny

242

Mam Decimal('3.9')jako część obiektu i chcę zakodować to w ciągu JSON, który powinien wyglądać {'x': 3.9}. Nie dbam o precyzję po stronie klienta, więc float jest w porządku.

Czy istnieje dobry sposób na serializację tego? JSONDecoder nie akceptuje obiektów dziesiętnych, a wcześniejsza konwersja do {'x': 3.8999999999999999}liczby zmiennoprzecinkowej daje błędne wyniki i będzie dużym marnotrawstwem przepustowości.

Knio
źródło
2
powiązany błąd w Pythonie: koder json nie może obsłużyć dziesiętnych
jfs
3,899999999999999999 nie jest bardziej błędny niż 3,4. 0.2 nie ma dokładnej reprezentacji pływaka.
Jasen
@Jasen 3.89999999999 jest około 12,8% bardziej błędny niż 3,4. Standard JSON dotyczy tylko serializacji i notacji, a nie implementacji. Używanie IEEE754 nie jest częścią surowej specyfikacji JSON, jest tylko najczęstszym sposobem jej implementacji. Implementacja wykorzystująca tylko precyzyjną arytmetykę dziesiętną jest całkowicie (a nawet ściślej) zgodna.
hraban
😂 mniej źle. ironiczny.
hraban

Odpowiedzi:

147

Co powiesz na podklasę 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)

Następnie użyj go w następujący sposób:

json.dumps({'x': decimal.Decimal('5.5')}, cls=DecimalEncoder)
Michał Marczyk
źródło
Właśnie zauważyłem, że tak naprawdę to nie będzie działać. Będzie odpowiednio edytować. (Pomysł pozostaje ten sam.)
Michał Marczyk,
Problem polegał na tym, że DecimalEncoder()._iterencode(decimal.Decimal('3.9')).next()zwrócił poprawny '3.9', ale DecimalEncoder()._iterencode(3.9).next()zwrócił obiekt generatora, który zwróciłby się tylko '3.899...'wtedy, gdy ułożyłeś stos na innym .next(). Generator zabawny biznes. No cóż ... Teraz powinno działać.
Michał Marczyk,
8
Nie możesz po prostu return (str(o),)zamiast tego? [o]jest listą zawierającą tylko 1 element, po co zawracać sobie nią głowę?
mpen
2
@Mark: return (str(o),)zwróci krotkę o długości 1, a kod w odpowiedzi zwróci generator długości 1. Zobacz dokumentację iterencode ()
Abgan
30
Ta implementacja już nie działa. Jeden Elias Zamaria pracuje nad tym samym stylem.
piro
223

Simplejson 2.1 i nowsze mają natywną obsługę typu dziesiętnego:

>>> json.dumps(Decimal('3.9'), use_decimal=True)
'3.9'

Należy pamiętać, że use_decimaljest Truedomyślnie:

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

Więc:

>>> json.dumps(Decimal('3.9'))
'3.9'

Mamy nadzieję, że ta funkcja będzie zawarta w standardowej bibliotece.

Lukas Cenovsky
źródło
7
Hmm, dla mnie to przekształca obiekty dziesiętne na liczby zmiennoprzecinkowe, co jest niedopuszczalne. Na przykład utrata precyzji podczas pracy z walutą.
Matthew Schinckel
12
@MatthewSchinckel Myślę, że nie. W rzeczywistości tworzy z niego ciąg. A jeśli podasz wynikowy ciąg z powrotem do json.loads(s, use_decimal=True)niego, otrzymasz z powrotem dziesiętną liczbę. Brak ruchu w całym procesie. Edytowano powyżej odpowiedzi. Mam nadzieję, że oryginalny plakat jest w porządku.
Shekhar
1
Aha, myślę, że też nie używałem use_decimal=Trueobciążeń.
Matthew Schinckel,
1
Dla mnie json.dumps({'a' : Decimal('3.9')}, use_decimal=True)daje '{"a": 3.9}'. Czy cel nie był '{"a": "3.9"}'?
MrJ
5
simplejson.dumps(decimal.Decimal('2.2'))działa również: brak wyraźnych use_decimal(testowane na simplejson / 3.6.0). Innym sposobem na załadowanie go z powrotem jest: json.loads(s, parse_float=Decimal)tzn. Możesz go odczytać za pomocą stdlib json(i simplejsonobsługiwane są również stare wersje).
jfs
181

Chciałbym poinformować wszystkich, że wypróbowałem odpowiedź Michała Marczyka na moim serwerze WWW, na którym działał Python 2.6.5 i działało dobrze. Jednak zaktualizowałem do Pythona 2.7 i przestał działać. Próbowałem wymyślić jakiś sposób na zakodowanie obiektów dziesiętnych i oto co wymyśliłem:

import decimal

class DecimalEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, decimal.Decimal):
            return float(o)
        return super(DecimalEncoder, self).default(o)

Mam nadzieję, że powinno to pomóc każdemu, kto ma problemy z Python 2.7. Przetestowałem to i wydaje się, że działa dobrze. Jeśli ktoś zauważy jakieś błędy w moim rozwiązaniu lub wymyśli lepszy sposób, daj mi znać.

Elias Zamaria
źródło
4
Python 2.7 zmienił zasady zaokrąglania liczb zmiennoprzecinkowych, więc to działa. Zobacz dyskusję w stackoverflow.com/questions/1447287/…
Nelson
2
Dla tych z nas, którzy nie mogą korzystać z simplejson (np. W Google App Engine), ta odpowiedź jest wybawieniem.
Joel Cross
17
Użyj unicodelub strzamiast, floataby zapewnić precyzję.
Seppo Erviälä
2
Problem z 54.3999 ... był ważny w Pythonie 2.6.x i starszych, gdzie konwersja zmiennoprzecinkowa na łańcuch nie działała regularnie, ale konwersja dziesiętna na łańcuch jest znacznie bardziej niepoprawna, ponieważ byłaby serializowana jako łańcuch z podwójnymi cudzysłowami "54.4", a nie jako numer.
hynekcer
1
Działa w python3
SeanFromIT
43

W mojej aplikacji Flask, która używa Pythona 2.7.11, alchemii kolby (z typami „db.decimal”) i Flask Marshmallow (dla serializatora „natychmiastowego” i deserializatora), miałem ten błąd, za każdym razem, gdy robiłem GET lub POST . Serializator i deserializator nie mógł przekonwertować typów dziesiętnych na żaden możliwy do zidentyfikowania format JSON.

Zrobiłem „pip install simplejson”, a potem po prostu dodając

import simplejson as json

serializator i deserializator znów zaczynają mruczeć. Nie zrobiłem nic innego ... DEciamls są wyświetlane jako format zmiennoprzecinkowy „234,00”.

ISONecroMAn
źródło
1
najłatwiejsza poprawka
SMDC
1
Co dziwne, nie musisz nawet importować simplejson- wystarczy sama instalacja. Początkowo wspomniany w tej odpowiedzi .
bsplosion
To nie działa na mnie, a mimo to dostałem to Decimal('0.00') is not JSON serializable po zainstalowaniu przez pip. Taka sytuacja występuje, gdy używasz zarówno pianki piankowej, jak i grafenu. Gdy zapytanie jest wywoływane w interfejsie API spoczynku, pianka prawidłowa działa w przypadku pól dziesiętnych. Jednak po wywołaniu z grafql zgłosił is not JSON serializablebłąd.
Roel,
Fantastyczne, wspaniałe,
Spiderman
Idealny! Działa to w sytuacjach, gdy używasz modułu napisanego przez kogoś innego, którego nie możesz łatwo zmodyfikować (w moim przypadku gspread do korzystania z Arkuszy Google)
happyskeptic
32

Próbowałem przejść z simplejson na wbudowany json dla GAE 2.7 i miałem problemy z przecinkiem. Jeśli domyślnie zwrócił str (o), istniały cudzysłowy (ponieważ _iterencode wywołuje _iterencode na wynikach domyślnych), a float (o) usuwa końcowe 0.

Jeśli default zwraca obiekt klasy, który dziedziczy po float (lub cokolwiek, co wywołuje repr bez dodatkowego formatowania) i ma niestandardową metodę __repr__, wydaje się, że działa tak, jak tego chcę.

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]'
Tesdal
źródło
Miły! Dzięki temu wartość dziesiętna kończy się w JSON jako zmiennoprzecinkowa Javascript, bez uprzedniego zaokrąglenia go przez Python do najbliższej wartości zmiennoprzecinkowej.
konrad
3
Niestety nie działa to w najnowszych Python 3. Istnieje teraz kod szybkiej ścieżki, który traktuje wszystkie podklasy float jako zmiennoprzecinkowe i nie wywołuje na nich repr.
Antti Haapala
@AnttiHaapala, przykład działa dobrze na Pythonie 3.6.
Cristian Ciupitu
@CristianCiupitu, rzeczywiście, nie wydaje mi się, że mogę teraz odtworzyć złe zachowanie
Antti Haapala,
2
Rozwiązanie przestało działać od wersji 3.5.2rc1, patrz github.com/python/cpython/commit/… . Jest float.__repr__zakodowany (który traci precyzję) i fakefloat.__repr__wcale nie jest wywoływany. Powyższe rozwiązanie działa poprawnie dla Pythona do wersji 3.5.1, jeśli fakefloat ma dodatkową metodę def __float__(self): return self.
myroslav
30

Brakuje natywnej opcji, więc dodam ją dla następnego faceta / żółcia, który jej szuka.

Począwszy od wersji Django 1.7.x jest wbudowana funkcja DjangoJSONEncoder, z której można go pobrać 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!

Javier Buzzi
źródło
Chociaż dobrze jest wiedzieć, że OP nie pytało o Django?
std''OrgnlDave
4
@ std''OrgnlDave masz 100% poprawność. Zapomniałem, jak się tu dostałem, ale przeszukałem to pytanie z „django” dołączonym do wyszukiwanego hasła i to pojawiło się, po trochę google, znalazłem odpowiedź i dodałem ją tutaj dla następnej osoby takiej jak ja, która natknęła się na it
Javier Buzzi
6
ratujesz
14

Moje 0,02 $!

Rozszerzam wiązkę kodera JSON, ponieważ serializuję mnóstwo danych dla mojego serwera WWW. Oto fajny kod. Pamiętaj, że można go łatwo rozszerzyć na dowolny format danych i odtworzysz 3.9 as"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

Ułatwia mi życie ...

std''OrgnlDave
źródło
3
Jest to niepoprawne: odtworzy 3,9 jako "thing": "3.9".
Glyph
najlepsze rozwiązania ze wszystkich, bardzo proste, dzięki, że uratowałeś mi dzień, dla mnie wystarczy, aby zapisać liczbę, ciąg znaków dziesiętnych jest w porządku
stackdave
@Glyph za pośrednictwem standardów JSON (których jest kilka ...), niecytowana liczba jest zmiennoprzecinkową podwójnej precyzji, a nie liczbą dziesiętną. Cytując to jedyny sposób na zagwarantowanie zgodności.
std''OrgnlDave
2
masz na to powód? Każda specyfikacja, którą przeczytałem, sugeruje, że jest zależna od implementacji.
Glyph
12

3.9nie może być dokładnie reprezentowany w pływakach IEEE, zawsze przyjdzie, ponieważ 3.8999999999999999np. spróbuj print repr(3.9), możesz przeczytać więcej o tym tutaj:

http://en.wikipedia.org/wiki/Floating_point
http://docs.sun.com/source/806-3568/ncg_goldberg.html

Jeśli więc nie chcesz liczb zmiennoprzecinkowych, tylko opcję musisz wysłać jako ciąg znaków i aby umożliwić automatyczną konwersję obiektów dziesiętnych na JSON, wykonaj coś takiego:

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)
Anurag Uniyal
źródło
Wiem, że nie będzie to 3,9 wewnętrznie po przeanalizowaniu go na kliencie, ale 3,9 to poprawna zmienna JSON. tzn. json.loads("3.9")zadziała i chciałbym, żeby tak było
Knio
@Anurag Miałeś na myśli repr (obj) zamiast repr (o) w twoim przykładzie.
orokusaki
Czy to nie umrze, jeśli spróbujesz zakodować coś, co nie jest dziesiętne?
mikemaccana
1
@nailer, nie, nie będzie, możesz tego spróbować, ponieważ domyślnie podniesiono wyjątek, aby zasygnalizować, że należy użyć następnego
modułu
1
Zobacz odpowiedź mikez302 - w Pythonie 2.7 lub nowszym nie ma to już zastosowania.
Joel Cross
9

Dla użytkowników Django :

Ostatnio natknąłem się na TypeError: Decimal('2337.00') is not JSON serializable kodowanie JSON tjjson.dumps(data)

Rozwiązanie :

# converts Decimal, Datetime, UUIDs to str for Encoding
from django.core.serializers.json import DjangoJSONEncoder  

json.dumps(response.data, cls=DjangoJSONEncoder)

Ale teraz wartość dziesiętna będzie ciągiem, teraz możemy jawnie ustawić parser wartości dziesiętnej / zmiennoprzecinkowej podczas dekodowania danych, używając parse_floatopcji w json.loads:

import decimal 

data = json.loads(data, parse_float=decimal.Decimal) # default is float(num_str)
Nabeel Ahmed
źródło
8

Ze standardowego dokumentu JSON , podlinkowanego w json.org :

JSON jest agnostykiem co do semantyki liczb. W dowolnym języku programowania może istnieć wiele różnych typów różnych pojemności i uzupełnień, stałych lub zmiennych, binarnych lub dziesiętnych. Może to utrudnić wymianę między różnymi językami programowania. Zamiast tego JSON oferuje tylko reprezentację liczb używanych przez ludzi: ciąg cyfr. Wszystkie języki programowania wiedzą, jak zrozumieć sekwencje cyfr, nawet jeśli nie zgadzają się co do wewnętrznych reprezentacji. To wystarczy, aby umożliwić wymianę.

Tak więc w JSON dokładne jest przedstawianie Dziesiętnych jako liczb (a nie ciągów znaków). Poniżej znajduje się możliwe rozwiązanie problemu.

Zdefiniuj niestandardowy koder JSON:

import json


class CustomJsonEncoder(json.JSONEncoder):

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

Następnie użyj go podczas serializacji danych:

json.dumps(data, cls=CustomJsonEncoder)

Jak zauważono w komentarzach do innych odpowiedzi, starsze wersje Pythona mogą zepsuć reprezentację podczas konwersji na zmiennoprzecinkowe, ale tak już nie jest.

Aby odzyskać przecinek dziesiętny w Pythonie:

Decimal(str(value))

To rozwiązanie jest wskazane w dokumentacji języka dziesiętnego w języku Python 3.0 :

Aby utworzyć liczbę dziesiętną z liczby zmiennoprzecinkowej, najpierw przekonwertuj ją na ciąg.

Hugo Mota
źródło
2
To nie jest „stałe” w Pythonie 3. konwersji na float zawsze sprawia, że można stracić reprezentację dziesiętną, i będzie prowadzić do rozbieżności. Jeśli Decimalużycie jest ważne, myślę, że lepiej jest używać ciągów znaków.
juanpa.arrivillaga
Uważam, że jest to bezpieczne, odkąd python 3.1. Utrata precyzji może być szkodliwa w operacjach arytmetycznych, ale w przypadku kodowania JSON, po prostu generujesz ciąg wyświetlania wartości, więc precyzja jest więcej niż wystarczająca dla większości przypadków użycia. Wszystko w JSON jest już ciągiem, więc umieszczanie cudzysłowów wokół wartości po prostu przeczy specyfikacji JSON.
Hugo Mota,
Powiedziawszy to, rozumiem obawy związane z przejściem na pływak. Prawdopodobnie istnieje inna strategia do użycia z koderem w celu uzyskania pożądanego ciągu wyświetlania. Mimo to nie wydaje mi się, żeby warto było podawać wartość.
Hugo Mota,
@ HugoMota „Wszystko w JSON jest już ciągiem, więc umieszczanie cudzysłowów wokół wartości po prostu przeczy specyfikacji JSON”. Nie: rfc-editor.org/rfc/rfc8259.txt - JSON to tekstowy format kodowania, ale to nie znaczy, że wszystko w nim należy interpretować jako ciąg znaków. Specyfikacja określa sposób kodowania liczb, niezależnie od ciągów.
Gunnar Þór Magnússon
@ GunnarÞórMagnússon „JSON to tekstowy format kodowania” - o to mi chodziło z „wszystko jest ciągiem znaków”. Wcześniejsze przekształcenie liczb na ciąg nie magicznie zachowa precyzji, ponieważ i tak stanie się ciągiem, gdy stanie się JSON. Zgodnie ze specyfikacją liczby nie zawierają cudzysłowów. Obowiązkiem czytelnika jest zachowanie precyzji podczas czytania (nie cytat, tylko moje zdanie).
Hugo Mota
6

To właśnie otrzymałem z naszej klasy

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)

Który przechodzi nieprzystosowany:

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)
James Lin
źródło
json.loads(myString, cls=CommonJSONEncoder)komentarz powinien byćjson.loads(myString, cls=CommonJSONDecoder)
Can Kavaklıoğlu,
hook_obiektu wymaga domyślnej wartości zwracanej, jeśli obiekt nie jest dziesiętny.
Can Kavaklıoğlu
3

Możesz utworzyć niestandardowy koder JSON zgodnie ze swoimi wymaganiami.

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)

Dekoder można tak nazwać,

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

i wynik będzie:

>>'{"x": 3.9}'
wróbel
źródło
super ... Dzięki za rozwiązanie one stop (y)
muhammed basil
To naprawdę działa! Dziękujemy za udostępnienie rozwiązania
tthreetorch
3

Dla tych, którzy nie chcą korzystać z biblioteki innej firmy ... Problem z odpowiedzią Eliasa Zamarii polega na tym, że konwertuje się ona na float, co może powodować problemy. Na przykład:

>>> json.dumps({'x': Decimal('0.0000001')}, cls=DecimalEncoder)
'{"x": 1e-07}'
>>> json.dumps({'x': Decimal('100000000000.01734')}, cls=DecimalEncoder)
'{"x": 100000000000.01733}'

JSONEncoder.encode()Metoda pozwala powrócić literalną treść json, w przeciwieństwie JSONEncoder.default(), który wrócisz typ zgodny json (jak pływaka), który następnie zostaje zakodowany w normalny sposób. Problem encode()polega na tym, że (normalnie) działa tylko na najwyższym poziomie. Ale nadal jest użyteczny, z odrobiną dodatkowej pracy (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)

Co daje ci:

>>> json.dumps({'x': Decimal('0.0000001')}, cls=DecimalEncoder)
'{"x": 0.0000001}'
>>> json.dumps({'x': Decimal('100000000000.01734')}, cls=DecimalEncoder)
'{"x": 100000000000.01734}'
ecp
źródło
2

Na podstawie odpowiedzi stdOrgnlDave zdefiniowałem to opakowanie, że można go wywoływać z opcjonalnymi rodzajami, aby koder działał tylko dla niektórych rodzajów w twoich projektach. Uważam, że praca powinna zostać wykonana wewnątrz twojego kodu i nie należy używać tego „domyślnego” kodera, ponieważ „jest on bardziej jawny niż niejawny”, ale rozumiem, że użycie tego pozwoli zaoszczędzić trochę twojego czasu. :-)

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()
Juanmi Taboada
źródło
0

Jeśli chcesz przekazać słownik zawierający dziesiętne do requestsbiblioteki (używając jsonargumentu słowa kluczowego), wystarczy zainstalować 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')})

Przyczyną problemu jest to, że requestsużywa simplejsontylko wtedy, gdy jest obecny, i wraca do wbudowanego, jsonjeśli nie jest zainstalowany.

Max Malysh
źródło
-6

można to zrobić poprzez dodanie

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

w \Lib\json\encoder.py:JSONEncoder._iterencode, ale miałem nadzieję na lepsze rozwiązanie

Knio
źródło
5
Możesz podklasować JSONEncoder jak na przykładzie powyżej, edytowanie zainstalowanych plików Pythona z ustalonej biblioteki lub samego interpretera powinno być ostatecznością.
justanr