Python JSON ทำให้เป็นวัตถุฐานสิบ


242

ฉันมีDecimal('3.9')เป็นส่วนหนึ่งของวัตถุและต้องการการเข้ารหัสนี้ให้สตริง JSON {'x': 3.9}ซึ่งควรมีลักษณะดังนี้ ฉันไม่สนใจเกี่ยวกับความแม่นยำในฝั่งไคลเอ็นต์ดังนั้นการลอยก็ดี

มีวิธีที่ดีในการทำให้เป็นอนุกรมนี้หรือไม่ JSONDecoder ไม่ยอมรับวัตถุทศนิยมและแปลงเป็นทศนิยมก่อนหน้าให้ผลตอบแทน{'x': 3.8999999999999999}ที่ผิดและจะเสียแบนด์วิดท์ขนาดใหญ่


2
ข้อผิดพลาด Python ที่เกี่ยวข้อง: ตัวเข้ารหัส json ไม่สามารถจัดการทศนิยม
jfs

3.8999999999999999 ไม่ผิดมากกว่า 3.4 คือ 0.2 ไม่มีตัวแทนลอยแน่นอน
Jasen

@Jasen 3.89999999999 นั้นผิดประมาณ 12.8% มากกว่า 3.4 คือ มาตรฐาน JSON นั้นเกี่ยวกับการทำให้เป็นอนุกรมและสัญลักษณ์เท่านั้นไม่ใช่การใช้งาน การใช้ IEEE754 ไม่ได้เป็นส่วนหนึ่งของข้อมูลจำเพาะ JSON แบบดิบมันเป็นวิธีที่ใช้กันทั่วไปมากที่สุดเท่านั้น การใช้งานที่ใช้เฉพาะเลขทศนิยมทศนิยมที่แม่นยำนั้นสมบูรณ์ (ในความเป็นจริงยิ่งเข้มงวดยิ่งขึ้น)
hraban

wrong ผิดน้อยลง แดกดัน
hraban

คำตอบ:


147

วิธีการเกี่ยวกับ subclassing 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)

โอ้ฉันเพิ่งสังเกตเห็นว่ามันใช้งานไม่ได้จริง ๆ จะแก้ไขตาม (ความคิดยังคงเหมือนเดิม)
Michał Marczyk

แต่ปัญหาก็คือว่าDecimalEncoder()._iterencode(decimal.Decimal('3.9')).next()กลับมาที่ถูกต้อง'3.9'แต่DecimalEncoder()._iterencode(3.9).next()กลับวัตถุกำเนิดเท่านั้นที่จะกลับมาเมื่อคุณซ้อนบนอีก'3.899...' .next()เครื่องกำเนิดไฟฟ้าธุรกิจตลก อืม ... ควรจะใช้งานได้แล้ว
Michał Marczyk

8
คุณreturn (str(o),)แทนไม่ได้เหรอ [o]รายการที่มีเพียงองค์ประกอบเดียวคือเหตุใดจึงต้องห่วงกับมัน
mpen

2
@ Mark: return (str(o),)จะคืนค่าความยาว 1 ในขณะที่โค้ดในคำตอบจะให้กำเนิดความยาว 1 ดูเอกสารiterencode ()
Abgan

30
การใช้งานนี้ไม่ทำงานอีกต่อไป หนึ่งในอีเลียสซามาเรียเป็นคนที่ทำงานในสไตล์เดียวกัน
piro

223

Simplejson 2.1ขึ้นไปมีการรองรับเนทิฟสำหรับประเภททศนิยม:

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

หวังว่าคุณสมบัตินี้จะรวมอยู่ในห้องสมุดมาตรฐาน


7
อืมสำหรับฉันนี่เป็นการแปลงวัตถุทศนิยมให้ลอยซึ่งไม่เป็นที่ยอมรับ สูญเสียความแม่นยำเมื่อทำงานกับสกุลเงินเป็นต้น
Matthew Schinckel

12
@ Matthewschinckel ฉันคิดว่ามันไม่ มันทำให้สตริงเป็นจริง และถ้าคุณดึงสตริงผลลัพธ์กลับมาjson.loads(s, use_decimal=True)มันจะให้ทศนิยมกลับมา ไม่มีการลอยในกระบวนการทั้งหมด แก้ไขคำตอบข้างต้น หวังว่าโปสเตอร์ดั้งเดิมจะใช้ได้กับมัน
Shekhar

1
อ่าฉันคิดว่าฉันไม่ได้ใช้use_decimal=Trueกับของหนักเกินไป
Matthew Schinckel

1
สำหรับผมจะช่วยให้json.dumps({'a' : Decimal('3.9')}, use_decimal=True) '{"a": 3.9}'เป้าหมายไม่ใช่'{"a": "3.9"}'หรือ
MrJ

5
simplejson.dumps(decimal.Decimal('2.2'))ยังใช้งานได้: ไม่ชัดเจนuse_decimal(ทดสอบกับ simplejson / 3.6.0) อีกวิธีในการโหลดกลับมาคือ: json.loads(s, parse_float=Decimal)คุณสามารถอ่านได้โดยใช้ stdlib json(และsimplejsonยังรองรับรุ่นเก่าด้วย)
jfs

181

ฉันต้องการให้ทุกคนรู้ว่าฉันได้ลองคำตอบของMichał Marczyk บนเว็บเซิร์ฟเวอร์ของฉันที่ใช้ Python 2.6.5 และมันใช้งานได้ดี อย่างไรก็ตามฉันอัพเกรดเป็น Python 2.7 และหยุดทำงาน ฉันพยายามคิดวิธีเข้ารหัสรหัสทศนิยมและนี่คือสิ่งที่ฉันคิด:

import decimal

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

หวังว่านี่จะช่วยทุกคนที่มีปัญหากับ Python 2.7 ฉันทดสอบแล้วและดูเหมือนว่าจะทำงานได้ดี หากใครพบข้อผิดพลาดใด ๆ ในโซลูชันของฉันหรือหาวิธีที่ดีกว่าโปรดแจ้งให้เราทราบ


4
Python 2.7 เปลี่ยนกฎสำหรับการปัดเศษทศนิยมเพื่อให้ทำงานได้ ดูการสนทนาในstackoverflow.com/questions/1447287/…
เนลสัน

2
สำหรับพวกเราที่ไม่สามารถใช้ simplejson (เช่นบน Google App Engine) คำตอบนี้คือสวรรค์
Joel Cross

17
ใช้unicodeหรือstrแทนfloatเพื่อความแม่นยำ
Seppo Erviälä

2
ปัญหากับ 54.3999 ... มีความสำคัญใน Python 2.6.x และรุ่นเก่ากว่าที่การแปลงลอยตัวเป็นสตริงไม่ทำงานเป็นประจำ แต่การแปลงทศนิยมเป็น str ไม่ถูกต้องมากนักเพราะมันจะต่อเนื่องกันเป็นสตริงด้วยเครื่องหมายคำพูดคู่"54.4"ไม่ใช่ หมายเลข.
hynekcer

1
ทำงานใน python3
SeanFromIT

43

ในแอพ Flask ของฉันซึ่งใช้ python 2.7.11, alchemy flask (ด้วยประเภท 'db.decimal') และ Flask Marshmallow (สำหรับ serializer และ deserializer 'ทันที') ฉันมีข้อผิดพลาดนี้ทุกครั้งที่ฉันได้ GET หรือ POST . serializer และ deserializer ไม่สามารถแปลงชนิดทศนิยมให้เป็นรูปแบบที่ระบุได้ของ JSON

ฉันได้ "pip install simplejson" จากนั้นเพียงแค่เพิ่ม

import simplejson as json

serializer และ deserializer เริ่มเสียงฟี้อย่างแมวอีกครั้ง ฉันไม่ทำอะไรเลย ... DEciamls แสดงเป็นรูปแบบลอยตัว '234.00'


1
การแก้ไขที่ง่ายที่สุด
SMDC

1
ผิดปกติพอคุณไม่ต้องนำเข้าsimplejson- เพียงแค่ติดตั้งมันจะหลอกลวง พูดถึงตอนแรกด้วยคำตอบนี้
bsplosion

สิ่งนี้ไม่ทำงานกับฉันและยังคงได้รับDecimal('0.00') is not JSON serializable หลังจากติดตั้งผ่านทาง pip สถานการณ์นี้เกิดขึ้นเมื่อคุณใช้ทั้งมาร์ชเมลโล่และกราฟีน เมื่อแบบสอบถามถูกเรียกบน api ส่วนที่เหลือ marshmallow ทำงานได้อย่างคาดหวังสำหรับเขตข้อมูลทศนิยม อย่างไรก็ตามเมื่อมันถูกเรียกด้วย graphql มันทำให้เกิดis not JSON serializableข้อผิดพลาด
Roel

สุดยอดเยี่ยม
อร์แมน

ที่สมบูรณ์แบบ! ใช้งานได้ในสถานการณ์ที่คุณใช้โมดูลที่เขียนโดยบุคคลอื่นซึ่งคุณไม่สามารถแก้ไขได้อย่างง่ายดาย (ในกรณีของฉัน gspread สำหรับการใช้ Google ชีต)
happyskeptic

32

ฉันลองเปลี่ยนจาก simplejson เป็น builtin json สำหรับ GAE 2.7 และมีปัญหากับทศนิยม หากค่าเริ่มต้นส่งคืน str (o) มีเครื่องหมายคำพูด (เพราะ _iterencode เรียก _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]'

ดี! สิ่งนี้ทำให้แน่ใจว่าค่าทศนิยมสิ้นสุดลงใน JSON ในรูปของ Javascript float โดยไม่มี Python ปัดเศษเป็นค่าทศนิยมที่ใกล้ที่สุด
konrad

3
น่าเสียดายที่นี่ไม่ได้ผลใน Python 3 ล่าสุด ขณะนี้มีโค้ดพา ธ ด่วนที่พิจารณาว่าคลาสย่อยทั้งหมดเป็นแบบลอยตัวและไม่เรียกการพิมพ์ซ้ำทั้งหมด
Antti Haapala

@AnttiHaapala ตัวอย่างทำงานได้ดีบน Python 3.6
Cristian Ciupitu

@CristianCiupitu แน่นอนฉันดูเหมือนจะไม่สามารถทำซ้ำพฤติกรรมที่ไม่ดีในขณะนี้
Antti Haapala

2
วิธีการแก้ปัญหาหยุดทำงานตั้งแต่ v3.5.2rc1 ดูgithub.com/python/cpython/commit/... มีfloat.__repr__ฮาร์ดโค้ด (ที่สูญเสียความแม่นยำ) และfakefloat.__repr__ไม่ได้ถูกเรียกเลย การแก้ปัญหาดังกล่าวข้างต้นไม่ทำงานอย่างถูกต้องสำหรับ python3 ถึง 3.5.1 ถ้า fakefloat def __float__(self): return selfมีวิธีการเพิ่มเติม
myroslav

30

ตัวเลือกเนทิฟหายไปดังนั้นฉันจะเพิ่มสำหรับผู้ชายคนถัดไปที่ค้นหา

เริ่มตั้งแต่วันที่ Django 1.7.x มีในตัวที่คุณจะได้รับจากDjangoJSONEncoderdjango.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)

โอมเพี้ยง!


แม้ว่าจะเป็นสิ่งที่ดีที่รู้ OP ไม่ได้ถามเกี่ยวกับ Django?
std''OrgnlDave

4
@ std''Orgnl บันทึกคุณถูกต้อง 100% ฉันลืมว่าฉันมาที่นี่ได้อย่างไร แต่ฉัน googled คำถามนี้ด้วย "django" ที่แนบมากับคำค้นหาและสิ่งนี้เกิดขึ้นหลังจาก googling อีกเล็กน้อยฉันพบคำตอบและเพิ่มที่นี่สำหรับคนต่อไปเช่นฉันที่สะดุด มัน
Javier Buzzi

6
คุณประหยัดวันของฉัน
gaozhidf

14

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

ทำให้ชีวิตของฉันง่ายขึ้นมาก ...


3
"thing": "3.9"นี้ไม่ถูกต้องมันจะทำซ้ำเป็น 3.9
Glyph

ทางออกที่ดีที่สุดของทั้งหมดง่ายมากขอบคุณคุณบันทึกวันของฉันสำหรับฉันก็เพียงพอที่จะบันทึกหมายเลขในสายอักขระสำหรับทศนิยมเป็น ok
stackdave

@ glyph ผ่านมาตรฐาน JSON (ซึ่งมีเพียงไม่กี่ ... ) ตัวเลขที่ไม่ได้ระบุเป็นทศนิยมที่มีความแม่นยำสองเท่าไม่ใช่ตัวเลขทศนิยม การอ้างอิงเป็นวิธีเดียวที่จะรับประกันความเข้ากันได้
std''OrgnlDave

2
คุณมีหนังเรื่องนี้ไหม สเป็คทุกครั้งที่ฉันอ่านหมายความว่ามันขึ้นอยู่กับการนำไปใช้
Glyph

12

3.9ไม่สามารถแสดงได้อย่างแน่นอนใน IEEE ลอยตัวมันจะเกิดขึ้นเสมอ3.8999999999999999เช่นลอง print repr(3.9)คุณสามารถอ่านเพิ่มเติมได้ที่นี่:

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

ดังนั้นหากคุณไม่ต้องการโฟลตมีเพียงตัวเลือกเดียวเท่านั้นที่คุณต้องส่งเป็นสตริงและเพื่อให้สามารถแปลงวัตถุทศนิยมให้เป็น 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)

ฉันรู้ว่ามันจะไม่เป็น 3.9 ภายในเมื่อมีการแยกวิเคราะห์ในไคลเอนต์ แต่ 3.9 เป็นลอย JSON ที่ถูกต้อง คือjson.loads("3.9")จะใช้งานได้และฉันต้องการให้เป็นเช่นนี้
Knio

@Anurag คุณหมายถึง repr (obj) แทนที่จะเป็น repr (o) ในตัวอย่างของคุณ
orokusaki

นี่จะไม่ตายหรือเปล่าถ้าคุณลองเข้ารหัสสิ่งที่ไม่ใช่ทศนิยม?
mikemaccana

1
@ thumbnailer ไม่ไม่คุณสามารถลองเหตุผลที่เป็นค่าเริ่มต้นยกข้อยกเว้นเพื่อส่งสัญญาณว่าควรใช้ตัวจัดการถัดไป
Anurag Uniyal

1
ดูคำตอบของ mikez302 - ใน Python 2.7 หรือสูงกว่าสิ่งนี้ไม่สามารถใช้ได้อีก
Joel Cross

9

สำหรับผู้ใช้ Django :

เพิ่งเจอกันTypeError: Decimal('2337.00') is not JSON serializable ในขณะที่การเข้ารหัส JSON เช่นjson.dumps(data)

วิธีแก้ปัญหา :

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

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

แต่ตอนนี้ค่าทศนิยมจะเป็นสตริงตอนนี้เราสามารถตั้งค่าตัวแยกวิเคราะห์ทศนิยม / ทศนิยมอย่างชัดเจนเมื่อถอดรหัสข้อมูลโดยใช้parse_floatตัวเลือกในjson.loads:

import decimal 

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

8

จากเอกสารมาตรฐาน JSONดังที่ลิงก์ในjson.org :

JSON ไม่เชื่อเรื่องพระเจ้าเกี่ยวกับความหมายของตัวเลข ในภาษาการเขียนโปรแกรมใด ๆ อาจมีหลากหลายประเภทของความสามารถและความหลากหลายที่หลากหลายคงที่หรือลอยตัวไบนารีหรือทศนิยม ที่สามารถทำการแลกเปลี่ยนระหว่างภาษาการเขียนโปรแกรมที่แตกต่างกันได้ยาก JSON จะเสนอเฉพาะตัวเลขที่มนุษย์ใช้แทน: ลำดับของตัวเลข ภาษาการเขียนโปรแกรมทั้งหมดรู้วิธีทำให้ความรู้สึกของลำดับเลขแม้ว่าพวกเขาไม่เห็นด้วยกับการเป็นตัวแทนภายใน นั่นก็เพียงพอแล้วที่จะยอมให้มีการแลกเปลี่ยน

ดังนั้นจึงเป็นความจริงที่ถูกต้องในการแสดงทศนิยมเป็นตัวเลข (แทนที่จะเป็นสตริง) ใน JSON ซอลเบลโลว์คือคำตอบของปัญหาที่เป็นไปได้

กำหนดตัวเข้ารหัส JSON ที่กำหนดเอง:

import json


class CustomJsonEncoder(json.JSONEncoder):

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

จากนั้นใช้เมื่อทำการซีเรียลข้อมูลของคุณ:

json.dumps(data, cls=CustomJsonEncoder)

ตามที่ระบุไว้จากความคิดเห็นเกี่ยวกับคำตอบอื่น ๆ ไพ ธ อนรุ่นเก่าอาจเลอะการเป็นตัวแทนเมื่อแปลงเป็นลอย แต่นั่นไม่ใช่กรณีอีกต่อไป

เพื่อให้ได้ทศนิยมใน Python:

Decimal(str(value))

วิธีการแก้ปัญหานี้มีการพูดถึงในเอกสาร Python 3.0 เกี่ยวกับทศนิยม :

ในการสร้างทศนิยมจากทศนิยมก่อนอื่นให้แปลงเป็นสตริง


2
สิ่งนี้ไม่ได้ "คงที่" ใน Python 3 การแปลงค่าให้float จำเป็นจะทำให้คุณสูญเสียการแทนค่าทศนิยมและจะนำไปสู่ความคลาดเคลื่อน หากDecimalมีความสำคัญต่อการใช้งานฉันคิดว่าการใช้สายจะดีกว่า
juanpa.arrivillaga

ฉันเชื่อว่ามันปลอดภัยที่จะทำสิ่งนี้ตั้งแต่ python 3.1 การสูญเสียความแม่นยำอาจเป็นอันตรายในการดำเนินการทางคณิตศาสตร์ แต่ในกรณีของการเข้ารหัส JSON คุณเพียงแค่สร้างการแสดงสตริงของค่าดังนั้นความแม่นยำนั้นเพียงพอสำหรับกรณีใช้งานส่วนใหญ่ ทุกอย่างใน JSON เป็นสตริงอยู่แล้วดังนั้นการใส่เครื่องหมายคำพูดล้อมรอบค่านั้นเป็นเพียงการท้าทายข้อมูลจำเพาะของ JSON
Hugo Mota

จากที่กล่าวมาฉันเข้าใจความกังวลเกี่ยวกับการเปลี่ยนใจเลื่อมลอย อาจมีกลยุทธ์ที่แตกต่างในการใช้กับเครื่องเข้ารหัสเพื่อสร้างสตริงการแสดงผลที่ต้องการ ถึงกระนั้นฉันไม่คิดว่ามันคุ้มค่าที่จะสร้างมูลค่าที่ยกมา
Hugo Mota

@HugoMota "ทุกอย่างใน JSON เป็นสตริงอยู่แล้วดังนั้นการใส่เครื่องหมายคำพูดล้อมรอบค่านั้นเป็นเพียงแค่ท้าทายข้อมูลจำเพาะของ JSON" ไม่: rfc-editor.org/rfc/rfc8259.txt - JSON เป็นรูปแบบการเข้ารหัสข้อความ แต่นั่นไม่ได้หมายความว่าทุกอย่างในนั้นจะถูกตีความว่าเป็นสตริง ข้อมูลจำเพาะกำหนดวิธีการเข้ารหัสตัวเลขแยกต่างหากจากสตริง
Gunnar

@ GunnarÞórMagnússon "JSON เป็นรูปแบบการเข้ารหัสข้อความ" - นั่นคือสิ่งที่ฉันหมายถึง "ทุกอย่างเป็นสตริง" การแปลงตัวเลขเป็นสตริงล่วงหน้าจะไม่รักษาความแม่นยำอย่างน่าอัศจรรย์เพราะมันจะเป็นสตริงอยู่แล้วเมื่อมันกลายเป็น JSON และตามข้อมูลจำเพาะตัวเลขไม่มีเครื่องหมายคำพูดล้อมรอบ มันเป็นความรับผิดชอบของผู้อ่านในการรักษาความแม่นยำในขณะที่อ่าน (ไม่ใช่คำพูด
Hugo Mota

6

นี่คือสิ่งที่ฉันมีสกัดจากชั้นเรียนของเรา

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.loads(myString, cls=CommonJSONEncoder)ความคิดเห็นควรjson.loads(myString, cls=CommonJSONDecoder)
Can Kavaklıoğlu

object_hook ต้องการค่าส่งคืนเริ่มต้นถ้า obj ไม่ใช่ทศนิยม
Can Kavaklıoğlu

3

คุณสามารถสร้างตัวเข้ารหัส JSON ที่กำหนดเองตามความต้องการของคุณ

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)

ตัวถอดรหัสสามารถถูกเรียกเช่นนี้

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

และผลลัพธ์จะเป็น:

>>'{"x": 3.9}'

ดีเลิศ ... ขอบคุณสำหรับโซลูชันครบวงจร (y)
muhammed basil

มันใช้งานได้จริง! ขอบคุณที่แบ่งปันโซลูชันของคุณ
tthreetorch

3

สำหรับผู้ที่ไม่ต้องการใช้ห้องสมุดบุคคลที่สาม ... ปัญหาของคำตอบของอีเลียสซามาเรียก็คือมันแปลงเป็นแบบลอยซึ่งสามารถพบปัญหาได้ ตัวอย่างเช่น:

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

JSONEncoder.encode()วิธีช่วยให้คุณกลับเนื้อหา JSON ที่แท้จริงแตกต่างจากJSONEncoder.default()ที่ได้คุณกลับ JSON ชนิดเข้ากันได้ (เช่น float) ที่แล้วได้รับการเข้ารหัสในวิธีปกติ ปัญหาที่เกิดขึ้นกับencode()มันคือ (ปกติ) จะทำงานที่ระดับบนสุดเท่านั้น แต่ก็ยังใช้งานได้โดยมีงานพิเศษเล็กน้อย (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)

ซึ่งให้คุณ:

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

2

จากคำตอบstdOrgnlDaveฉันได้กำหนด wrapper นี้ว่ามันสามารถเรียกได้ด้วยชนิดที่เป็นทางเลือกดังนั้นตัวเข้ารหัสจะทำงานกับบางชนิดภายในโครงการของคุณเท่านั้น ฉันเชื่อว่างานควรทำภายในรหัสของคุณและไม่ควรใช้ตัวเข้ารหัส "ค่าเริ่มต้น" เนื่องจากดีกว่าชัดเจนกว่า "แต่ฉันเข้าใจว่าการใช้สิ่งนี้จะช่วยประหยัดเวลาของคุณได้ :-)

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

0

หากคุณต้องการส่งพจนานุกรมที่มีทศนิยมให้กับrequestsห้องสมุด (ใช้jsonอาร์กิวเมนต์คำหลัก) คุณเพียงแค่ต้องติดตั้ง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')})

สาเหตุของปัญหาคือการrequestsใช้simplejsonเฉพาะถ้ามันมีอยู่และกลับไปที่ในตัวjsonหากไม่ได้ติดตั้ง


-6

สิ่งนี้สามารถทำได้โดยการเพิ่ม

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

ใน\Lib\json\encoder.py:JSONEncoder._iterencodeแต่ฉันหวังว่าจะได้ทางออกที่ดีกว่า


5
คุณสามารถ subclass JSONEncoder ตามตัวอย่างข้างบนการแก้ไขไฟล์ Python ที่ติดตั้งของไลบรารีที่สร้างขึ้นหรือล่ามเองควรเป็นทางเลือกสุดท้าย
justanr
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.