wake-up-neo.com

Python JSON serialisiert ein Decimal-Objekt

Ich habe eine Decimal('3.9') als Teil eines Objekts und möchte diese in eine JSON-Zeichenfolge codieren, die wie folgt aussehen sollte: {'x': 3.9}. Die 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 Decimal-Objekte und die vorherige Konvertierung in ein Float ergibt {'x': 3.8999999999999999} was falsch ist und eine große Verschwendung von Bandbreite sein wird.

199
Knio

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 wie folgt:

json.dumps({'x': decimal.Decimal('5.5')}, cls=DecimalEncoder)
131
Michał Marczyk

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_decimal ist standardmäßig 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):

So:

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

Hoffentlich wird diese Funktion in die Standardbibliothek aufgenommen.

199
Lukas Cenovsky

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 es hat gut funktioniert. Ich habe jedoch ein Upgrade auf Python 2.7 durchgeführt und es funktioniert nicht mehr. Ich habe versucht, mir eine Art Codierung von Dezimalobjekten auszudenken, und dabei habe ich mir Folgendes ausgedacht:

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 allen helfen, die Probleme mit Python 2.7 haben. 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.

156
Elias Zamaria

In meiner Flask App, die python 2.7.11 verwendet, flask Alchemy (mit 'db.decimal'-Typen) , und Flask Marshmallow (für "Instant" Serializer und Deserializer), ich hatte diesen Fehler, jedes Mal, wenn ich ein GET oder POST. Der Serializer und Deserializer konnte nicht konvertieren Decimal-Typen in JSON identifizierbares Format.

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

import simplejson as json

der Serializer und Deserializer fängt wieder an zu schnurren. Ich habe nichts anderes getan ... DEciamls werden im Float-Format '234.00' angezeigt.

30
ISONecroMAn

Ich habe versucht, für GAE 2.7 von simplejson auf builtin json zu wechseln, und hatte Probleme mit der Dezimalzahl. Wenn default str (o) zurückliefe, gäbe es Anführungszeichen (weil _iterencode _iterencode für die Ergebnisse von default aufruft), und float (o) würde die nachgestellte 0 entfernen.

Wenn default ein Objekt einer Klasse zurückgibt, die von float erbt (oder irgendetwas, 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]'
27
tesdal

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

Ab Django 1.7.x gibt es ein eingebautes DjangoJSONEncoder, das Sie aus Django.core.serializers.json Beziehen können.

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!

16
Javier Buzzi

3.9 Kann in IEEE-Floats nicht genau dargestellt werden. Es wird immer 3.8999999999999999 Ausgegeben, z. versuche print repr(3.9), hier kannst du mehr darüber 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 um die automatische Konvertierung von Dezimalobjekten nach JSON zu ermöglichen, gehen Sie wie folgt 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)
11
Anurag Uniyal

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 und 3.9 als "thing": 3.9 Reproduziert.

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 ...

11
std''OrgnlDave

Für Django Benutzer:

Vor kurzem ist TypeError: Decimal('2337.00') is not JSON serializable während der JSON-Codierung aufgetreten, d. H. json.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)

Jetzt ist der Dezimalwert eine Zeichenfolge. Jetzt können wir den Parser für Dezimal-/Gleitkommawerte explizit festlegen, wenn Daten mit der Option parse_float In json.loads Decodiert werden:

import decimal 

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

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. Das kann den Austausch zwischen verschiedenen Programmiersprachen erschweren. JSON bietet stattdessen nur die Darstellung von Zahlen, die von Menschen verwendet werden: eine Folge von Ziffern. Alle Programmiersprachen verstehen es, Ziffernfolgen zu verstehen, auch wenn sie in internen Darstellungen nicht übereinstimmen. Das ist genug, um den Austausch zu ermöglichen.

Daher ist es tatsächlich korrekt, Dezimalstellen in JSON als Zahlen (und nicht als Zeichenfolgen) darzustellen. Unten sehen Sie 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 bei der Serialisierung Ihrer Daten:

json.dumps(data, cls=CustomJsonEncoder)

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

So rufen Sie die Dezimalstelle in Python zurück:

Decimal(str(value))

Diese Lösung finden Sie in Python 3.0-Dokumentation zu Dezimalstellen :

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

6
Hugo Mota

Dies ist, was ich aus unserer Klasse extrahiert habe

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 am wenigsten passt:

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)
6
James Lin

Basierend auf stdOrgnlDave Antwort Ich habe 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 bin der Meinung, dass die Arbeit in Ihrem Code erledigt werden sollte und dass Sie diesen "Standard" -Encoder nicht verwenden sollten, da "er besser explizit als implizit ist", aber ich verstehe, dass Sie dadurch etwas Zeit sparen. :-)

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 __== '__main__':
    JSONEncoder_newdefault()
2
Juanmi Taboada

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 genannt werden,

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

und die Ausgabe wird sein:

>>'{"x": 3.9}'
1
sparrow

Wenn Sie ein Wörterbuch mit Dezimalzahlen an die Bibliothek requests übergeben möchten (mit dem Schlüsselwortargument json), müssen Sie nur simplejson installieren:

$ 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 requestssimplejson nur verwendet, wenn es vorhanden ist, und auf das integrierte json zurückgreift, wenn es nicht installiert ist.

0
Max Malysh