Python JSON membuat serial objek desimal

242

Saya memiliki Decimal('3.9')sebagai bagian dari objek, dan ingin menyandikan ini ke string JSON yang seharusnya terlihat {'x': 3.9}. Saya tidak peduli tentang presisi di sisi klien, jadi float baik-baik saja.

Apakah ada cara yang baik untuk membuat cerita bersambung ini? JSONDecoder tidak menerima objek Desimal, dan mengkonversi ke float sebelumnya menghasilkan {'x': 3.8999999999999999}yang salah, dan akan menjadi pemborosan bandwidth yang besar.

Knio
sumber
2
bug Python terkait: json encoder tidak dapat menangani desimal
jfs
3,8999999999999999 tidak lebih salah dari 3.4 adalah. 0,2 tidak memiliki representasi float yang tepat.
Jasen
@Jasen 3.89999999999 sekitar 12.8% lebih salah dari 3.4. Standar JSON hanya tentang serialisasi dan notasi, bukan implementasi. Menggunakan IEEE754 bukan bagian dari spesifikasi JSON mentah, itu hanya cara paling umum untuk mengimplementasikannya. Implementasi yang hanya menggunakan aritmatika desimal tepat sepenuhnya (pada kenyataannya, bahkan lebih ketat) sesuai.
hraban
😂 kurang salah. ironis.
hraban

Jawaban:

147

Bagaimana dengan subklas 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)

Kemudian gunakan seperti ini:

json.dumps({'x': decimal.Decimal('5.5')}, cls=DecimalEncoder)
Michał Marczyk
sumber
Aduh, saya baru sadar bahwa itu tidak akan berfungsi seperti ini. Akan mengedit sesuai. (Idenya tetap sama.)
Michał Marczyk
Masalahnya adalah bahwa DecimalEncoder()._iterencode(decimal.Decimal('3.9')).next()mengembalikan yang benar '3.9', tetapi DecimalEncoder()._iterencode(3.9).next()mengembalikan objek generator yang hanya akan kembali '3.899...'ketika Anda menumpuk yang lain .next(). Bisnis pembangkit lucu. Oh well ... Harusnya bekerja sekarang.
Michał Marczyk
8
Tidak bisakah Anda hanya return (str(o),)sebaliknya? [o]adalah daftar dengan hanya 1 elemen, mengapa repot-repot mengulanginya?
mpen
2
@ Mark: return (str(o),)akan mengembalikan tuple dengan panjang 1, sedangkan kode dalam jawaban mengembalikan generator dengan panjang 1. Lihat iterencode () docs
Abgan
30
Implementasi ini tidak berfungsi lagi. Yang Elias Zamaria adalah yang mengerjakan dengan gaya yang sama.
piro
224

Simplejson 2.1 dan lebih tinggi memiliki dukungan asli untuk tipe Desimal:

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

Perhatikan bahwa use_decimalsecara Truedefault:

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

Begitu:

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

Semoga fitur ini akan dimasukkan dalam perpustakaan standar.

Lukas Cenovsky
sumber
7
Hmm, bagi saya ini mengubah objek Desimal menjadi mengapung, yang tidak dapat diterima. Kehilangan presisi saat bekerja dengan mata uang, misalnya.
Matthew Schinckel
12
@ MatthewSchinckel saya pikir tidak. Ini benar-benar membuat string dari itu. Dan jika Anda memberi makan string yang dihasilkan kembali ke json.loads(s, use_decimal=True)itu memberi Anda kembali desimal. Tidak mengapung di seluruh proses. Diedit jawaban di atas. Berharap poster asli tidak masalah dengan itu.
Shekhar
1
Aha, saya pikir saya tidak menggunakan use_decimal=Truebeban juga.
Matthew Schinckel
1
Bagi saya json.dumps({'a' : Decimal('3.9')}, use_decimal=True)memberi '{"a": 3.9}'. Apakah tujuannya bukan '{"a": "3.9"}'?
MrJ
5
simplejson.dumps(decimal.Decimal('2.2'))juga berfungsi: tidak ada yang eksplisit use_decimal(diuji pada simplejson / 3.6.0). Cara lain untuk memuatnya kembali adalah: json.loads(s, parse_float=Decimal)yaitu, Anda dapat membacanya menggunakan stdlib json(dan simplejsonversi lama juga didukung).
jfs
181

Saya ingin memberi tahu semua orang bahwa saya mencoba jawaban Michał Marczyk di server web saya yang menjalankan Python 2.6.5 dan berfungsi dengan baik. Namun, saya memutakhirkan ke Python 2.7 dan berhenti bekerja. Saya mencoba memikirkan semacam cara untuk menyandikan objek Desimal dan inilah yang saya buat:

import decimal

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

Semoga ini dapat membantu siapa saja yang memiliki masalah dengan Python 2.7. Saya mengujinya dan tampaknya berfungsi dengan baik. Jika ada yang melihat adanya bug dalam solusi saya atau mencari cara yang lebih baik, beri tahu saya.

Elias Zamaria
sumber
4
Python 2.7 mengubah aturan untuk pembulatan mengapung jadi ini berfungsi. Lihat diskusi di stackoverflow.com/questions/1447287/…
Nelson
2
Bagi kita yang tidak bisa menggunakan simplejson (mis. Di Google App Engine) jawaban ini adalah anugerah.
Joel Cross
17
Gunakan unicodeatau stralih-alih floatuntuk memastikan ketepatan.
Seppo Erviälä
2
Masalah dengan 54.3999 ... penting di Python 2.6.x dan lebih lama di mana konversi float ke string tidak bekerja secara teratur, tetapi konversi Desimal ke str jauh lebih tidak benar karena akan diserialisasi sebagai string dengan tanda kutip ganda "54.4", bukan sebagai sebuah angka.
hynekcer
1
Bekerja di python3
SeanFromIT
43

Dalam aplikasi Flask saya, Yang menggunakan python 2.7.11, alkimia flask (dengan tipe 'db.decimal'), dan Flask Marshmallow (untuk serializer dan deserializer 'instan'), saya mengalami kesalahan ini, setiap kali saya melakukan GET atau POST . Serializer dan deserializer, gagal mengubah tipe Desimal menjadi format JSON yang dapat diidentifikasi.

Saya melakukan "pip install simplejson", lalu Hanya dengan menambahkan

import simplejson as json

serializer dan deserializer mulai mendengkur lagi. Saya tidak melakukan apa-apa lagi ... DEciamls ditampilkan sebagai format float '234.00'.

ISONecroMAn
sumber
1
perbaikan termudah
SMDC
1
Anehnya, Anda bahkan tidak perlu mengimpor simplejson- hanya menginstalnya saja. Awalnya disebutkan oleh jawaban ini .
bsplosion
Ini tidak bekerja pada saya, dan masih dapat Decimal('0.00') is not JSON serializable setelah menginstalnya melalui pip. Situasi ini adalah ketika Anda menggunakan marshmallow dan graphene. Ketika kueri dipanggil pada api sisa, marshmallow bekerja dengan harapan untuk bidang desimal. Namun ketika dipanggil dengan graphql itu menimbulkan is not JSON serializablekesalahan.
Roel
Fantastis, Luar Biasa,
Spiderman
Sempurna! Ini berfungsi dalam situasi di mana Anda menggunakan modul yang ditulis oleh orang lain yang Anda tidak dapat dengan mudah memodifikasi (dalam kasus saya gspread untuk menggunakan Google Sheets)
happyskeptic
32

Saya mencoba beralih dari simplejson ke builtin json untuk GAE 2.7, dan memiliki masalah dengan desimal. Jika default mengembalikan str (o) ada tanda kutip (karena _iterencode memanggil _iterencode pada hasil default), dan float (o) akan menghapus trailing 0.

Jika default mengembalikan objek kelas yang diwarisi dari float (atau apa pun yang memanggil repr tanpa pemformatan tambahan) dan memiliki metode __repr__ khusus, sepertinya berfungsi seperti yang saya inginkan.

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
sumber
Bagus! Ini memastikan nilai desimal berakhir di JSON sebagai float Javascript, tanpa Python membulatkannya ke nilai float terdekat.
konrad
3
Sayangnya ini tidak berfungsi di Python 3 terbaru. Sekarang ada beberapa kode jalur cepat yang menganggap semua sub-kelas float sebagai float, dan tidak memanggil repr pada mereka sama sekali.
Antti Haapala
@AnttiHaapala, contohnya berfungsi dengan baik di Python 3.6.
Cristian Ciupitu
@CristianCiupitu memang, saya sepertinya tidak bisa mereproduksi perilaku buruk sekarang
Antti Haapala
2
Solusinya berhenti bekerja sejak v3.5.2rc1, lihat github.com/python/cpython/commit/… . Ada float.__repr__hardcoded (yang kehilangan presisi), dan fakefloat.__repr__tidak disebut sama sekali. Solusi di atas tidak berfungsi dengan baik untuk python3 hingga 3.5.1, jika fakefloat memiliki metode tambahan def __float__(self): return self.
myroslav
30

Opsi asli tidak ada sehingga saya akan menambahkannya untuk cowok / empedu berikutnya yang mencarinya.

Mulai dari Django 1.7.x ada built-in DjangoJSONEncoderyang bisa Anda dapatkan 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
sumber
Meskipun ini bagus untuk diketahui, OP tidak bertanya tentang Django?
std''OrgnlDave
4
@ std''OrgnlDapatkan Anda 100% benar. Saya lupa bagaimana saya sampai di sini, tetapi saya googled pertanyaan ini dengan "Django" terlampir pada istilah pencarian dan ini muncul, setelah sedikit lebih googling, saya menemukan jawabannya dan menambahkannya di sini untuk orang berikutnya seperti saya, yang tersandung di seberang itu
Javier Buzzi
6
Anda menghemat hari saya
gaozhidf
14

$ 0,02 saya!

Saya memperpanjang sekelompok enkoder JSON karena saya membuat serialisasi banyak data untuk server web saya. Ini beberapa kode yang bagus. Perhatikan bahwa mudah diperluas ke hampir semua format data yang Anda suka dan akan mereproduksi 3,9 sebagai"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

Membuat hidup saya jauh lebih mudah ...

std''OrgnlDave
sumber
3
Ini tidak benar: itu akan mereproduksi 3,9 sebagai "thing": "3.9".
Glyph
solusi terbaik dari semuanya, sangat sederhana, terima kasih Anda telah menyelamatkan hari saya, bagi saya sudah cukup untuk menyimpan nomor, dalam string untuk desimal ok
stackdave
@Glyph melalui standar JSON (yang ada beberapa ...), angka yang tidak dikutip adalah floating-point presisi ganda, bukan angka desimal. Mengutip itu adalah satu-satunya cara untuk menjamin kompatibilitas.
std''OrgnlDave
2
apakah Anda memiliki kutipan untuk ini? Setiap spek yang saya baca menyiratkan bahwa itu tergantung pada implementasi.
Glyph
12

3.9tidak dapat direpresentasikan secara tepat di IEEE float, itu akan selalu datang seperti 3.8999999999999999, misalnya coba print repr(3.9), Anda dapat membaca lebih lanjut di sini:

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

Jadi jika Anda tidak ingin float, hanya opsi yang harus Anda kirimkan sebagai string, dan untuk memungkinkan konversi objek desimal otomatis ke JSON, lakukan sesuatu seperti ini:

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
sumber
Saya tahu itu tidak akan 3,9 secara internal setelah diuraikan pada klien, tetapi 3,9 adalah pelampung JSON yang valid. yaitu, json.loads("3.9")akan bekerja, dan saya ingin ini
Knio
@Anurag Yang Anda maksud adalah repr (obj) bukan repr (o) dalam contoh Anda.
orokusaki
Bukankah ini hanya akan mati jika Anda mencoba dan menyandikan sesuatu yang bukan desimal?
mikemaccana
1
@ thumbnailer, tidak itu tidak akan, Anda bisa mencobanya, alasannya default meningkatkan pengecualian untuk sinyal bahwa pengendali selanjutnya harus digunakan
Anurag Uniyal
1
Lihat jawaban mikez302 - dalam Python 2.7 atau lebih tinggi, ini tidak lagi berlaku.
Joel Cross
9

Untuk pengguna Django :

Baru-baru ini menemukan TypeError: Decimal('2337.00') is not JSON serializable sementara yaitu JSON encodingjson.dumps(data)

Solusi :

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

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

Tapi, sekarang nilai Desimal akan menjadi string, sekarang kita dapat secara eksplisit mengatur parser nilai desimal / float saat mendekode data, menggunakan parse_floatopsi di json.loads:

import decimal 

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

Dari Dokumen Standar JSON , sebagaimana ditautkan di json.org :

JSON adalah agnostik tentang semantik angka. Dalam bahasa pemrograman apa pun, bisa ada berbagai jenis jumlah berbagai kapasitas dan komplemen, tetap atau mengambang, biner atau desimal. Itu dapat membuat pertukaran antar bahasa pemrograman yang berbeda menjadi sulit. JSON sebagai gantinya hanya menawarkan representasi angka yang digunakan manusia: urutan digit. Semua bahasa pemrograman tahu bagaimana memahami urutan digit meskipun mereka tidak setuju dengan representasi internal. Itu cukup untuk memungkinkan pertukaran.

Jadi sebenarnya akurat untuk menggambarkan Desimal sebagai angka (bukan string) di JSON. Di bawah ini terletak solusi yang mungkin untuk masalah tersebut.

Tentukan pembuat JSON khusus:

import json


class CustomJsonEncoder(json.JSONEncoder):

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

Kemudian gunakan saat membuat serialisasi data Anda:

json.dumps(data, cls=CustomJsonEncoder)

Seperti dicatat dari komentar pada jawaban lain, versi python yang lebih lama mungkin mengacaukan representasi ketika mengonversi menjadi float, tetapi itu tidak terjadi lagi.

Untuk mendapatkan kembali desimal dengan Python:

Decimal(str(value))

Solusi ini diisyaratkan dalam dokumentasi Python 3.0 pada desimal :

Untuk membuat Desimal dari float, pertama-tama ubah menjadi string.

Hugo Mota
sumber
2
Ini bukan "diperbaiki" dalam Python 3. Konversi ke float tentu membuat Anda kehilangan representasi desimal, dan akan menyebabkan perbedaan. Jika Decimalpenting untuk digunakan, saya pikir lebih baik menggunakan string.
juanpa.arrivillaga
Saya percaya ini aman untuk dilakukan sejak python 3.1. Hilangnya presisi mungkin berbahaya dalam operasi aritmatika, tetapi dalam kasus pengkodean JSON, Anda hanya menghasilkan tampilan string nilai, sehingga presisi lebih dari cukup untuk sebagian besar kasus penggunaan. Semua yang ada di JSON sudah menjadi string, jadi menempatkan tanda kutip di sekitar nilai hanya menentang spesifikasi JSON.
Hugo Mota
Dengan mengatakan itu, saya memahami keprihatinan tentang konversi ke float. Mungkin ada strategi berbeda untuk digunakan dengan pembuat enkode untuk menghasilkan string tampilan yang diinginkan. Namun, saya tidak berpikir itu layak menghasilkan nilai kuotasi.
Hugo Mota
@HugoMota "Segala sesuatu di JSON sudah menjadi string, jadi menempatkan tanda kutip di sekitar nilai hanya menentang spesifikasi JSON." Tidak: rfc-editor.org/rfc/rfc8259.txt - JSON adalah format pengodean berbasis teks, tetapi itu tidak berarti semua yang ada di dalamnya harus ditafsirkan sebagai string. Spesifikasi menentukan cara menyandikan angka, secara terpisah dari string.
Gunnar Þór Magnússon
@ GunnarÞórMagnússon "JSON adalah format pengodean berbasis teks" - itulah yang saya maksud dengan "semuanya adalah string". Mengonversi angka ke string sebelumnya tidak akan secara ajaib mempertahankan presisi karena bagaimanapun juga akan menjadi string ketika menjadi JSON. Dan menurut spesifikasi, angka tidak memiliki tanda kutip di sekitarnya. Adalah tanggung jawab pembaca untuk menjaga ketepatan saat membaca (bukan kutipan, hanya pendapat saya).
Hugo Mota
6

Inilah yang saya miliki, diambil dari kelas kami

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)

Yang lolos paling tidak:

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
sumber
json.loads(myString, cls=CommonJSONEncoder)komentar harusjson.loads(myString, cls=CommonJSONDecoder)
Dapat Kavaklıoğlu
object_hook membutuhkan nilai pengembalian default jika obj tidak desimal.
Can Kavaklıoğlu
3

Anda dapat membuat enkode JSON khusus sesuai kebutuhan Anda.

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)

Decoder bisa disebut seperti ini,

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

dan hasilnya adalah:

>>'{"x": 3.9}'
burung gereja
sumber
luar biasa ... Terima kasih atas solusi satu atap (y)
muhammed basil
Ini benar-benar berfungsi! Terima kasih telah membagikan solusi Anda
tthreetorch
3

Bagi mereka yang tidak ingin menggunakan perpustakaan pihak ketiga ... Masalah dengan jawaban Elias Zamaria adalah bahwa itu dikonversi menjadi mengambang, yang dapat mengalami masalah. Sebagai contoh:

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

The JSONEncoder.encode()Metode memungkinkan Anda mengembalikan konten json literal, seperti JSONEncoder.default()yang telah Anda kembali jenis kompatibel json (seperti float) yang kemudian akan dikodekan dengan cara biasa. Masalah dengan encode()itu (biasanya) hanya bekerja di tingkat atas. Tapi itu masih bisa digunakan, dengan sedikit kerja ekstra (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)

Yang memberi Anda:

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

Berdasarkan jawaban stdOrgnlDave, saya telah mendefinisikan pembungkus ini yang dapat dipanggil dengan jenis opsional sehingga pembuat enkode hanya akan bekerja untuk jenis tertentu di dalam proyek Anda. Saya percaya pekerjaan harus dilakukan di dalam kode Anda dan tidak menggunakan encoder "default" ini karena "itu lebih baik eksplisit daripada implisit", tapi saya mengerti menggunakan ini akan menghemat waktu Anda. :-)

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

Jika Anda ingin meneruskan kamus berisi desimal ke requestsperpustakaan (menggunakan jsonargumen kata kunci), Anda hanya perlu menginstal 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')})

Alasan masalahnya adalah yang hanya requestsdigunakan simplejsonjika ada, dan kembali ke bawaan jsonjika tidak diinstal.

Max Malysh
sumber
-6

ini bisa dilakukan dengan menambahkan

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

masuk \Lib\json\encoder.py:JSONEncoder._iterencode, tapi saya berharap untuk solusi yang lebih baik

Knio
sumber
5
Anda dapat subkelas JSONEncoder seperti yang dicontohkan di atas, mengedit file Python yang terinstal dari pustaka yang mapan atau penerjemah itu sendiri harus menjadi pilihan terakhir.
justanr