Python JSON serialisiert ein Dezimalobjekt

242

Ich habe ein Decimal('3.9')als Teil eines Objekts und möchte dies in eine JSON-Zeichenfolge codieren, die aussehen sollte {'x': 3.9}. Präzision auf der Client-Seite ist mir egal, daher ist ein Float in Ordnung.

Gibt es eine gute Möglichkeit, dies zu serialisieren? JSONDecoder akzeptiert keine Dezimalobjekte, und die vorherige Konvertierung in einen Float ergibt {'x': 3.8999999999999999}eine falsche Leistung und ist eine große Verschwendung von Bandbreite.

Knio
quelle
2
verwandter Python-Fehler: JSON-Encoder kann keine Dezimalstellen verarbeiten
jfs
3.8999999999999999 ist nicht falscher als 3.4. 0.2 hat keine exakte Float-Darstellung.
Jasen
@Jasen 3.89999999999 ist etwa 12,8% falscher als 3.4. Beim JSON-Standard geht es nur um Serialisierung und Notation, nicht um Implementierung. Die Verwendung von IEEE754 ist nicht Teil der JSON-Rohspezifikation, sondern nur die häufigste Methode zur Implementierung. Eine Implementierung, die nur präzise Dezimalarithmetik verwendet, ist vollständig (sogar noch strenger) konform.
Hraban
😂 weniger falsch. ironisch.
Hraban

Antworten:

147

Wie wäre es mit Unterklassen 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)

Dann benutze es so:

json.dumps({'x': decimal.Decimal('5.5')}, cls=DecimalEncoder)
Michał Marczyk
quelle
Autsch, ich habe gerade bemerkt, dass es so nicht funktioniert. Wird entsprechend bearbeitet. (Die Idee bleibt jedoch dieselbe.)
Michał Marczyk
Das Problem war, dass DecimalEncoder()._iterencode(decimal.Decimal('3.9')).next()das korrekte zurückgegeben wurde '3.9', aber DecimalEncoder()._iterencode(3.9).next()ein Generatorobjekt zurückgegeben wurde, das nur zurückgegeben wurde, '3.899...'wenn Sie ein anderes gestapelt hatten .next(). Generator lustiges Geschäft. Na ja ... sollte jetzt funktionieren.
Michał Marczyk
8
Kannst du nicht einfach return (str(o),)stattdessen? [o]ist eine Liste mit nur 1 Element, warum sollte man sich die Mühe machen, sie zu durchlaufen?
Mpen
2
@Mark: return (str(o),)würde Tupel der Länge 1 zurückgeben, während Code in der Antwort Generator der Länge 1 zurückgibt.
Abgan
30
Diese Implementierung funktioniert nicht mehr. Elias Zamarias ist derjenige, der am selben Stil arbeitet.
Piro
223

Simplejson 2.1 und höher bietet native Unterstützung für den Dezimaltyp:

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

Beachten Sie, dass use_decimalist Truestandardmäßig:

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

So:

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

Hoffentlich wird diese Funktion in der Standardbibliothek enthalten sein.

Lukas Cenovsky
quelle
7
Hmm, für mich konvertiert dies Dezimalobjekte in Floats, was nicht akzeptabel ist. Zum Beispiel Präzisionsverlust bei der Arbeit mit Währungen.
Matthew Schinckel
12
@ MatthewSchinckel Ich denke, das tut es nicht. Es macht tatsächlich eine Saite daraus. Und wenn Sie die resultierende Zeichenfolge zurückgeben, erhalten json.loads(s, use_decimal=True)Sie die Dezimalstelle zurück. Kein Float im gesamten Prozess. Oben Antwort bearbeitet. Hoffe, das Originalplakat ist in Ordnung damit.
Shekhar
1
Aha, ich glaube, ich habe use_decimal=Truedie Lasten auch nicht benutzt.
Matthew Schinckel
1
Für mich json.dumps({'a' : Decimal('3.9')}, use_decimal=True)gibt '{"a": 3.9}'. War das Ziel nicht '{"a": "3.9"}'?
MrJ
5
simplejson.dumps(decimal.Decimal('2.2'))funktioniert auch: nicht explizit use_decimal(getestet auf simplejson / 3.6.0). Eine andere Möglichkeit, es zurückzuladen, ist: json.loads(s, parse_float=Decimal)Sie können es mit stdlib lesen json(und alte simplejsonVersionen werden ebenfalls unterstützt).
JFS
181

Ich möchte alle wissen lassen, dass ich Michał Marczyks Antwort auf meinem Webserver ausprobiert habe, auf dem Python 2.6.5 ausgeführt wurde, und dass es einwandfrei funktioniert hat. Ich habe jedoch ein Upgrade auf Python 2.7 durchgeführt und es funktioniert nicht mehr. Ich habe versucht, mir eine Möglichkeit zum Codieren von Dezimalobjekten auszudenken, und mir ist Folgendes eingefallen:

import decimal

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

Dies sollte hoffentlich jedem helfen, der Probleme mit Python 2.7 hat. Ich habe es getestet und es scheint gut zu funktionieren. Wenn jemand Fehler in meiner Lösung bemerkt oder einen besseren Weg findet, lassen Sie es mich bitte wissen.

Elias Zamaria
quelle
4
Python 2.7 hat die Regeln für das Runden von Floats geändert, damit dies funktioniert. Siehe Diskussion in stackoverflow.com/questions/1447287/…
Nelson
2
Für diejenigen von uns, die simplejson nicht verwenden können (dh auf Google App Engine), ist diese Antwort ein Geschenk Gottes.
Joel Cross
17
Verwenden Sie unicodeoder stranstelle von float, um Präzision zu gewährleisten.
Seppo Erviälä
2
Das Problem mit 54.3999 ... war in Python 2.6.x und älter wichtig, wo die Konvertierung von float in string nicht regelmäßig funktionierte, die Konvertierung von Decimal in str jedoch viel falscher ist, da sie als String mit doppelten Anführungszeichen "54.4"und nicht als eine Zahl.
Hynekcer
1
Funktioniert in Python3
SeanFromIT
43

In meiner Flask-App, die Python 2.7.11, Flask-Alchemie (mit 'db.decimal'-Typen) und Flask Marshmallow (für' Instant'-Serializer und -Deserializer) verwendet, trat dieser Fehler jedes Mal auf, wenn ich einen GET oder POST durchführte . Der Serializer und Deserializer konnte keine Dezimaltypen in ein JSON-identifizierbares Format konvertieren.

Ich habe eine "pip install simplejson" gemacht, dann einfach durch Hinzufügen

import simplejson as json

Der Serializer und Deserializer beginnt wieder zu schnurren. Ich habe nichts anderes getan ... DEciamls werden als Float-Format '234.00' angezeigt.

ISONecroMAn
quelle
1
die einfachste Lösung
SMDC
1
Seltsamerweise müssen Sie nicht einmal importieren simplejson- nur die Installation reicht aus. Zunächst durch diese Antwort erwähnt .
bsplosion
Dies funktioniert bei mir nicht und wurde Decimal('0.00') is not JSON serializable nach der Installation über pip immer noch angezeigt. Diese Situation ist, wenn Sie sowohl Marshmallow als auch Graphen verwenden. Wenn eine Abfrage für eine Rest-API aufgerufen wird, funktioniert Marshmallow erwartungsgemäß für Dezimalfelder. Wenn es jedoch mit graphql aufgerufen wird, wird ein is not JSON serializableFehler ausgelöst .
Roel
Fantastisch, großartig,
Spiderman
Perfekt! Dies funktioniert in Situationen, in denen Sie ein Modul verwenden, das von einer anderen Person geschrieben wurde und das Sie nicht einfach ändern können (in meinem Fall gspread für die Verwendung von Google Sheets)
happyskeptic
32

Ich habe versucht, für GAE 2.7 von simplejson auf integriertes json umzuschalten, und hatte Probleme mit der Dezimalstelle. Wenn default str (o) zurückgibt, gibt es Anführungszeichen (weil _iterencode _iterencode für die Ergebnisse von default aufruft), und float (o) würde die nachfolgende 0 entfernen.

Wenn default ein Objekt einer Klasse zurückgibt, die von float erbt (oder etwas, das repr ohne zusätzliche Formatierung aufruft) und eine benutzerdefinierte __repr__ -Methode hat, scheint es so zu funktionieren, wie ich es möchte.

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
quelle
Nett! Dadurch wird sichergestellt, dass der Dezimalwert als Javascript-Float im JSON landet, ohne dass Python ihn zuerst auf den nächsten Float-Wert rundet.
Konrad
3
Leider funktioniert dies in den letzten Python 3 nicht. Es gibt jetzt einen Fast-Path-Code, der alle Float-Unterklassen als Floats betrachtet und sie nicht als Repräsentanten aufruft.
Antti Haapala
@AnttiHaapala, das Beispiel funktioniert gut unter Python 3.6.
Cristian Ciupitu
@CristianCiupitu in der Tat, ich scheine nicht in der Lage zu sein, das schlechte Verhalten jetzt zu reproduzieren
Antti Haapala
2
Die Lösung funktioniert seit Version 3.5.2rc1 nicht mehr, siehe github.com/python/cpython/commit/… . Es ist fest float.__repr__codiert (das verliert an Präzision) und fakefloat.__repr__wird überhaupt nicht aufgerufen. Die obige Lösung funktioniert ordnungsgemäß für Python3 bis 3.5.1, wenn Fakefloat über eine zusätzliche Methode verfügt def __float__(self): return self.
Myroslav
30

Die native Option fehlt, also füge ich sie für den nächsten Kerl / die nächste Galle hinzu, die danach sucht.

Ab Django 1.7.x gibt es eine integrierte Funktion DjangoJSONEncoder, von der Sie sie erhalten können 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
quelle
Obwohl dies gut zu wissen ist, hat das OP nicht nach Django gefragt?
std''OrgnlDave
4
@ std''OrgnlDave du bist 100% korrekt. Ich habe vergessen, wie ich hierher gekommen bin, aber ich habe diese Frage mit "django" gegoogelt, das an den Suchbegriff angehängt ist, und dies kam auf, nachdem ich ein bisschen mehr gegoogelt hatte, fand ich die Antwort und fügte sie hier für die nächste Person wie mich hinzu, die darüber stolpert it
Javier Buzzi
6
Sie retten meinen Tag
Gaozhidf
14

Meine $ .02!

Ich erweitere eine Reihe von JSON-Encodern, da ich Tonnen von Daten für meinen Webserver serialisiere. Hier ist ein netter Code. Beachten Sie, dass es leicht auf so ziemlich jedes Datenformat erweiterbar ist, das Sie möchten, und 3.9 als reproduziert"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

Macht mein Leben so viel einfacher ...

std''OrgnlDave
quelle
3
Dies ist falsch: Es wird 3.9 als reproduziert "thing": "3.9".
Glyphe
Die besten Lösungen von allen, sehr einfach, danke, dass Sie meinen Tag gerettet haben, denn ich bin genug, um die Zahl zu speichern, in Zeichenfolge für Dezimalzahl ist ok
Stackdave
@Glyph über JSON-Standards (von denen es einige gibt ...) ist eine nicht zitierte Zahl ein Gleitkomma mit doppelter Genauigkeit, keine Dezimalzahl. Das Zitieren ist der einzige Weg, um die Kompatibilität zu gewährleisten.
std''OrgnlDave
2
Hast du ein Zitat dafür? Jede Spezifikation, die ich gelesen habe, impliziert, dass sie implementierungsabhängig ist.
Glyphe
12

3.9kann nicht genau in IEEE-Floats dargestellt werden, es wird immer so kommen 3.8999999999999999, zB versuchen print repr(3.9)Sie hier mehr darüber zu lesen:

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

Wenn Sie also kein Float möchten, müssen Sie es nur als Zeichenfolge senden und die automatische Konvertierung von Dezimalobjekten in JSON ermöglichen. Gehen Sie folgendermaßen vor:

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
quelle
Ich weiß, dass es intern nicht 3.9 sein wird, sobald es auf dem Client analysiert wurde, aber 3.9 ist ein gültiger JSON-Float. dh json.loads("3.9")wird funktionieren, und ich möchte, dass es so ist
Knio
@ Anurag Du hast in deinem Beispiel repr (obj) anstelle von repr (o) gemeint.
Orokusaki
Wird das nicht einfach sterben, wenn Sie versuchen, etwas zu codieren, das nicht dezimal ist?
Mikemaccana
1
@nailer, nein, wird es nicht, Sie können das versuchen, Grund ist die Standardausnahme Ausnahme, um zu signalisieren, dass der nächste Handler verwendet werden sollte
Anurag Uniyal
1
Siehe die Antwort von mikez302 - in Python 2.7 oder höher gilt dies nicht mehr.
Joel Cross
9

Für Django-Benutzer :

Vor kurzem kam TypeError: Decimal('2337.00') is not JSON serializable während JSON-Codierung dhjson.dumps(data)

Lösung :

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

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

Aber jetzt ist der Dezimalwert eine Zeichenfolge. Jetzt können wir den Dezimal- / Gleitkomma-Parser beim Dekodieren von Daten explizit festlegen, indem wir die parse_floatOption in verwenden json.loads:

import decimal 

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

Aus dem JSON-Standarddokument , wie in json.org verlinkt :

JSON ist agnostisch in Bezug auf die Semantik von Zahlen. In jeder Programmiersprache kann es eine Vielzahl von Zahlentypen mit unterschiedlichen Kapazitäten und Ergänzungen geben, fest oder schwebend, binär oder dezimal. Dies kann den Austausch zwischen verschiedenen Programmiersprachen erschweren. JSON bietet stattdessen nur die Darstellung von Zahlen, die Menschen verwenden: eine Folge von Ziffern. Alle Programmiersprachen wissen, wie man Ziffernfolgen versteht, auch wenn sie in internen Darstellungen nicht übereinstimmen. Das reicht aus, um den Austausch zu ermöglichen.

Es ist also tatsächlich korrekt, Dezimalstellen in JSON als Zahlen (anstatt als Zeichenfolgen) darzustellen. Unten liegt eine mögliche Lösung für das Problem.

Definieren Sie einen benutzerdefinierten 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)

Verwenden Sie es dann beim Serialisieren Ihrer Daten:

json.dumps(data, cls=CustomJsonEncoder)

Wie aus den Kommentaren zu den anderen Antworten hervorgeht, können ältere Versionen von Python die Darstellung beim Konvertieren in Float durcheinander bringen, aber das ist nicht mehr der Fall.

So erhalten Sie die Dezimalstelle in Python zurück:

Decimal(str(value))

Diese Lösung wird in der Python 3.0-Dokumentation zu Dezimalstellen angedeutet :

Um eine Dezimalstelle aus einem Gleitkommawert zu erstellen, konvertieren Sie sie zunächst in eine Zeichenfolge.

Hugo Mota
quelle
2
Dies ist nicht „fixiert“ in Python 3. zu konvertieren ein float notwendigerweise machen Sie die Dezimaldarstellung verlieren, und werden zu Abweichungen führen. Wenn Decimales wichtig ist, Zeichenfolgen zu verwenden, ist es meiner Meinung nach besser, Zeichenfolgen zu verwenden.
juanpa.arrivillaga
Ich glaube, dass dies seit Python 3.1 sicher ist. Der Genauigkeitsverlust kann bei arithmetischen Operationen schädlich sein. Bei der JSON-Codierung wird jedoch lediglich eine Zeichenfolgenanzeige des Werts erstellt, sodass die Genauigkeit für die meisten Anwendungsfälle mehr als ausreichend ist. Alles in JSON ist bereits eine Zeichenfolge, daher widerspricht das Setzen von Anführungszeichen um den Wert nur der JSON-Spezifikation.
Hugo Mota
Vor diesem Hintergrund verstehe ich die Bedenken hinsichtlich der Umstellung auf Float. Es gibt wahrscheinlich eine andere Strategie, um mit dem Encoder die gewünschte Anzeigezeichenfolge zu erzeugen. Trotzdem denke ich nicht, dass es sich lohnt, einen notierten Wert zu erstellen.
Hugo Mota
@HugoMota "Alles in JSON ist bereits eine Zeichenfolge, daher widerspricht das Setzen von Anführungszeichen um den Wert nur der JSON-Spezifikation." Nein: rfc-editor.org/rfc/rfc8259.txt - JSON ist ein textbasiertes Codierungsformat, aber das bedeutet nicht, dass alles darin als Zeichenfolge interpretiert werden soll. Die Spezifikation definiert, wie Zahlen getrennt von Zeichenfolgen codiert werden.
Gunnar Þór Magnússon
@ GunnarÞórMagnússon "JSON ist ein textbasiertes Codierungsformat" - das habe ich mit "alles ist eine Zeichenfolge" gemeint. Das vorherige Konvertieren der Zahlen in Zeichenfolgen bewahrt die Genauigkeit nicht auf magische Weise, da es sich ohnehin um eine Zeichenfolge handelt, wenn sie zu JSON wird. Und gemäß der Spezifikation haben Zahlen keine Anführungszeichen. Es liegt in der Verantwortung des Lesers, beim Lesen die Präzision zu wahren (kein Zitat, nur meine Meinung dazu).
Hugo Mota
6

Das habe ich aus unserer Klasse extrahiert

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)

Was unittest vergeht:

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
quelle
json.loads(myString, cls=CommonJSONEncoder)Kommentar sollte seinjson.loads(myString, cls=CommonJSONDecoder)
Kann Kavaklıoğlu
object_hook benötigt einen Standardrückgabewert, wenn obj nicht dezimal ist.
Kann Kavaklıoğlu
3

Sie können einen benutzerdefinierten JSON-Encoder gemäß Ihren Anforderungen erstellen.

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)

Der Decoder kann so aufgerufen werden,

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

und die Ausgabe wird sein:

>>'{"x": 3.9}'
Spatz
quelle
genial ... Danke für One-Stop-Lösung (y)
Muhuh Basilikum
Das funktioniert wirklich! Vielen Dank für das Teilen Ihrer Lösung
tthreetorch
3

Für diejenigen, die keine Bibliothek eines Drittanbieters verwenden möchten ... Ein Problem mit der Antwort von Elias Zamaria ist, dass sie in Float konvertiert wird, was zu Problemen führen kann. Beispielsweise:

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

Mit dieser JSONEncoder.encode()Methode können Sie den wörtlichen json-Inhalt zurückgeben, im Gegensatz dazu JSONEncoder.default(), dass Sie einen json-kompatiblen Typ (wie float) zurückgeben, der dann auf normale Weise codiert wird. Das Problem dabei encode()ist, dass es (normalerweise) nur auf der obersten Ebene funktioniert. Aber es ist immer noch verwendbar, mit ein wenig zusätzlicher Arbeit (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)

Welches gibt Ihnen:

>>> json.dumps({'x': Decimal('0.0000001')}, cls=DecimalEncoder)
'{"x": 0.0000001}'
>>> json.dumps({'x': Decimal('100000000000.01734')}, cls=DecimalEncoder)
'{"x": 100000000000.01734}'
ecp
quelle
2

Basierend auf der Antwort von stdOrgnlDave habe ich diesen Wrapper so definiert, dass er mit optionalen Arten aufgerufen werden kann, sodass der Encoder nur für bestimmte Arten in Ihren Projekten funktioniert. Ich glaube, die Arbeit sollte in Ihrem Code erledigt werden und nicht diesen "Standard" -Codierer zu verwenden, da "es besser explizit als implizit ist", aber ich verstehe, dass die Verwendung dieses Encoders einen Teil Ihrer Zeit spart. :-)

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
quelle
0

Wenn Sie ein Wörterbuch mit Dezimalstellen an die requestsBibliothek übergeben möchten (mithilfe des jsonSchlüsselwortarguments), müssen Sie lediglich Folgendes installieren 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')})

Der Grund für das Problem ist, dass es nur requestsverwendet wird, simplejsonwenn es vorhanden ist, und auf das integrierte System zurückgreift, jsonwenn es nicht installiert ist.

Max Malysh
quelle
-6

Dies kann durch Hinzufügen erfolgen

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

in \Lib\json\encoder.py:JSONEncoder._iterencode, aber ich hoffte auf eine bessere Lösung

Knio
quelle
5
Sie können JSONEncoder wie oben beschrieben in Unterklassen unterteilen. Das Bearbeiten der installierten Python-Dateien einer etablierten Bibliothek oder des Interpreters selbst sollte das allerletzte Mittel sein.
Justin