วิธีสร้างคลาส JSON ที่ทำให้เป็นอนุกรม


833

จะทำให้คลาส Python เป็นอนุกรมได้อย่างไร

คลาสง่าย ๆ :

class FileItem:
    def __init__(self, fname):
        self.fname = fname

ฉันควรทำอย่างไรเพื่อให้ได้ผลลัพธ์:

>>> import json

>>> my_file = FileItem('/foo/bar')
>>> json.dumps(my_file)
TypeError: Object of type 'FileItem' is not JSON serializable

โดยไม่มีข้อผิดพลาด


31
โชคไม่ดีที่คำตอบทั้งหมดดูเหมือนจะตอบคำถาม "ฉันจะทำให้ชั้นเป็นอันดับได้อย่างไร" ไม่ใช่คำถามเกี่ยวกับการกระทำ "ฉันจะสร้างคลาสที่สามารถทำให้เป็นอนุกรมได้อย่างไร" คำตอบเหล่านี้สมมติว่าคุณกำลังทำซีเรียลไลซ์เซชั่นด้วยตัวเองแทนที่จะส่งผ่านวัตถุไปยังโมดูลอื่น ๆ ที่ทำให้เป็นอนุกรม
Kyle Delaney

หากคุณใช้ Python3.5 + คุณสามารถใช้ jsons มันจะแปลงวัตถุของคุณ (และคุณลักษณะทั้งหมดของมันซ้ำ ) เพื่อ dict import jsonsดูคำตอบด้านล่าง - มันทำงานได้อย่างสมบูรณ์แบบ
tswaehn

คำตอบ:


551

คุณมีความคิดเกี่ยวกับผลลัพธ์ที่คาดหวังหรือไม่ เช่นจะทำเช่นนี้?

>>> f  = FileItem("/foo/bar")
>>> magic(f)
'{"fname": "/foo/bar"}'

json.dumps(f.__dict__)ในกรณีที่คุณเพียงสามารถเรียก

หากคุณต้องการผลลัพธ์ที่กำหนดเองมากขึ้นคุณจะต้อง subclass JSONEncoderและใช้การทำให้เป็นอันดับที่กำหนดเองของคุณเอง

สำหรับตัวอย่างเล็กน้อยดูด้านล่าง

>>> from json import JSONEncoder
>>> class MyEncoder(JSONEncoder):
        def default(self, o):
            return o.__dict__    

>>> MyEncoder().encode(f)
'{"fname": "/foo/bar"}'

จากนั้นคุณส่งคลาสนี้ไปยังjson.dumps()เมธอดเป็นclskwarg:

json.dumps(cls=MyEncoder)

หากคุณต้องการถอดรหัสคุณจะต้องกำหนดเองobject_hookให้กับJSONDecoderคลาส สำหรับเช่น

>>> def from_json(json_object):
        if 'fname' in json_object:
            return FileItem(json_object['fname'])
>>> f = JSONDecoder(object_hook = from_json).decode('{"fname": "/foo/bar"}')
>>> f
<__main__.FileItem object at 0x9337fac>
>>> 

44
การใช้__dict__จะไม่ทำงานในทุกกรณี หากยังไม่ได้ตั้งค่าแอตทริบิวต์หลังจากที่วัตถุถูกสร้างอินสแตนซ์__dict__อาจไม่ได้รับการเติมเต็ม ในตัวอย่างด้านบนคุณตกลง แต่ถ้าคุณมีแอตทริบิวต์คลาสที่คุณต้องการเข้ารหัสสิ่งเหล่านั้นจะไม่ถูกแสดงรายการ__dict__เว้นแต่ว่าพวกเขาได้รับการแก้ไขในการ__init__เรียกคลาสหรือโดยวิธีอื่นหลังจากวัตถุถูกสร้างอินสแตนซ์
Kris Hardy

8
+1 แต่from_json()ฟังก์ชั่นที่ใช้เป็น object-hook ควรมีelse: return json_objectคำสั่งเพื่อให้สามารถจัดการกับวัตถุทั่วไปได้เช่นกัน
jogojapan

8
@KrisHardy __dict__ก็ไม่สามารถใช้งานได้ถ้าคุณใช้__slots__กับสไตล์คลาสใหม่
badp

7
คุณสามารถใช้กำหนดเองJSONEncoderดังกล่าวข้างต้นเพื่อสร้างโพรโทคอลที่กำหนดเองเช่นการตรวจสอบการมีอยู่ของ__json_serializable__วิธีการและเรียกมันว่าจะได้รับการเป็นตัวแทน JSON อนุกรมของวัตถุ นี่น่าจะเป็นในการรักษาด้วยรูปแบบงูใหญ่อื่น ๆ เช่น__getitem__, __str__, และ__eq__ __len__
jpmc26

5
__dict__ก็จะไม่ทำงานซ้ำเช่นหากคุณลักษณะของวัตถุของคุณเป็นวัตถุอื่น
Neel

634

นี่คือวิธีแก้ปัญหาอย่างง่ายสำหรับฟีเจอร์ง่ายๆ:

.toJSON() วิธี

แทนที่จะเป็นคลาสที่สามารถทำให้เป็นอนุกรมของ JSON ให้ใช้วิธีการซีเรียลไลเซอร์:

import json

class Object:
    def toJSON(self):
        return json.dumps(self, default=lambda o: o.__dict__, 
            sort_keys=True, indent=4)

ดังนั้นคุณเพียงเรียกมันว่าเป็นอันดับ:

me = Object()
me.name = "Onur"
me.age = 35
me.dog = Object()
me.dog.name = "Apollo"

print(me.toJSON())

จะส่งออก:

{
    "age": 35,
    "dog": {
        "name": "Apollo"
    },
    "name": "Onur"
}

82
มีจำนวน จำกัด มาก หากคุณมี dict {"foo": "bar", "baz": "bat"} นั่นจะทำให้เป็นอันดับต่อ JSON ได้อย่างง่ายดาย หากคุณมี {"foo": "bar", "baz": MyObject ()} คุณจะไม่สามารถทำได้ สถานการณ์ที่เหมาะสมที่สุดคืออ็อบเจ็กต์ที่ซ้อนกันถูกจัดลำดับเป็น JSON แบบวนซ้ำไม่ชัดเจน
Mark E. Haase

30
มันจะยังคงทำงาน o.__dict___คุณกำลังขาดหายไป ลองตัวอย่างของคุณเอง: class MyObject(): def __init__(self): self.prop = 1 j = json.dumps({ "foo": "bar", "baz": MyObject() }, default=lambda o: o.__dict__)
Onur Yıldırım

14
โซลูชันนี้สามารถย้อนกลับได้หรือไม่ เช่นมันง่ายต่อการสร้างวัตถุจาก json หรือไม่?
Jorge Leitao

2
@ JCLeitãoไม่คุณสามารถมีสองคลาสที่แตกต่างกันด้วยฟิลด์เดียวกัน วัตถุ A และ B ของชั้นที่ (อาจจะมีคุณสมบัติเหมือนกัน) จะมีเหมือนกัน/a.__dict__ b.__dict__
Martin Thoma

7
สิ่งนี้ไม่ทำงานกับdatetime.datetimeอินสแตนซ์ เกิดข้อผิดพลาดดังต่อไปนี้:'datetime.datetime' object has no attribute '__dict__'
Bruno Finger

171

สำหรับคลาสที่ซับซ้อนมากขึ้นคุณสามารถพิจารณาเครื่องมือjsonpickle :

jsonpickle เป็นไลบรารี Python สำหรับการทำให้เป็นอนุกรมและการดีซีเรียลไลเซชันของวัตถุ Python ที่ซับซ้อนไปและกลับจาก JSON

ไลบรารี Python มาตรฐานสำหรับการเข้ารหัส Python ใน JSON เช่น json ของ stdlib, simplejson และ demjson สามารถจัดการได้เฉพาะ Python primitives ที่มี JSON ที่เทียบเท่าโดยตรง (เช่น dicts, รายการ, สตริง, ints ฯลฯ ) jsonpickle สร้างอยู่ด้านบนของไลบรารีเหล่านี้และอนุญาตให้โครงสร้างข้อมูลที่ซับซ้อนมากขึ้นเพื่ออนุกรมเป็น JSON jsonpickle สามารถกำหนดค่าได้อย่างมากและสามารถขยายได้ - ช่วยให้ผู้ใช้สามารถเลือกแบ็กเอนด์ JSON และเพิ่มแบ็กเอนด์เพิ่มเติม

(ลิงก์ไปยัง jsonpickle บน PyPi)


32
มาจาก C # นี่คือสิ่งที่ฉันคาดหวัง ซับง่ายหนึ่งและไม่ยุ่งกับชั้นเรียน
Jerther

2
jsonpickle ยอดเยี่ยม มันทำงานได้อย่างสมบูรณ์แบบสำหรับวัตถุขนาดใหญ่ที่ซับซ้อนและยุ่งเหยิงด้วยชั้นเรียนหลายระดับ
wisbucky

มีตัวอย่างวิธีที่เหมาะสมในการบันทึกไฟล์นี้หรือไม่? เอกสารประกอบแสดงวิธีการเข้ารหัสและถอดรหัสjsonpickleวัตถุเท่านั้น นอกจากนี้สิ่งนี้ไม่สามารถถอดรหัส dict ของ dicts ที่มี dataframes ของ pandas
user5359531

3
@ user5359531 คุณสามารถใช้และobj = jsonpickle.decode(file.read()) file.write(jsonpickle.encode(obj))
Kilian Batzner

1
คำถามเฉพาะสำหรับ django: การใช้ jsonpickle สำหรับการซีเรียลไลซ์เซชั่นข้อมูลมีความเสี่ยงเช่นเดียวกับของดอง? (ตามที่อธิบายไว้ที่นี่docs.djangoproject.com/en/1.11/topics/http/sessions/ ...... )?
พอลโบร์แมน

89

คำตอบส่วนใหญ่เกี่ยวข้องกับการเปลี่ยนการเรียกไปที่json.dumps ()ซึ่งไม่สามารถทำได้หรือเป็นที่ต้องการเสมอไป (อาจเกิดขึ้นภายในคอมโพเนนต์เฟรมเวิร์ก)

หากคุณต้องการที่จะสามารถเรียก json.dumps (obj)ตามที่เป็นอยู่แล้ววิธีง่ายๆคือการสืบทอดจากdict :

class FileItem(dict):
    def __init__(self, fname):
        dict.__init__(self, fname=fname)

f = FileItem('tasks.txt')
json.dumps(f)  #No need to change anything here

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


2
นี่อาจเป็นทางออกที่ดี :) ฉันเชื่อในกรณีของฉัน ประโยชน์: คุณสื่อสาร "รูปร่าง" ของวัตถุด้วยการทำให้ระดับกับ init มันเป็น serializable โดยเนื้อแท้และมันก็ดูเป็น interpretable Repr
PascalVKooten

1
แม้ว่า "dot-access" ยังคงหายไป :(
PascalVKooten

2
Ahh ที่ดูเหมือนว่าจะทำงาน! ขอบคุณไม่แน่ใจว่าทำไมนี่ไม่ใช่คำตอบที่ยอมรับได้ ฉันเห็นด้วยอย่างยิ่งว่าการเปลี่ยนแปลงdumpsไม่ใช่ทางออกที่ดี โดยวิธีการในกรณีส่วนใหญ่คุณอาจต้องการที่จะมีdictร่วมกันกับคณะผู้แทนมรดกซึ่งหมายความว่าคุณจะมีบางส่วนdictแอตทริบิวต์ประเภทภายในชั้นเรียนของคุณแล้วคุณจะผ่านแอตทริบิวต์นี้เป็นพารามิเตอร์เป็นสิ่งที่ initialisation super().__init__(self.elements)เช่น
cglacet

47

ฉันชอบคำตอบของ Onurแต่จะขยายเพื่อรวมtoJSON()วิธีการทางเลือกสำหรับวัตถุเพื่อทำให้เป็นอันดับตัวเอง:

def dumper(obj):
    try:
        return obj.toJSON()
    except:
        return obj.__dict__
print json.dumps(some_big_object, default=dumper, indent=2)

ฉันพบว่าสิ่งนี้เป็นความสมดุลที่ดีที่สุดระหว่างการใช้การจัดการที่มีอยู่json.dumpsและการแนะนำที่กำหนดเอง ขอบคุณ!
Daniel Buckmaster

12
ที่จริงผมชอบจริงๆ; แต่แทนที่จะtry-catchทำสิ่งที่ชอบif 'toJSON' in obj.__attrs__():... เพื่อหลีกเลี่ยงความล้มเหลวที่เงียบ (ในกรณีที่เกิดความล้มเหลวใน toJSON () ด้วยเหตุผลอื่นมากกว่าที่ไม่ได้อยู่ที่นั่น) ... ความล้มเหลวที่อาจนำไปสู่ความเสียหายของข้อมูล
thclark

39

ตัวเลือกอื่นคือการตัด JSON ทิ้งในคลาสของตัวเอง:

import json

class FileItem:
    def __init__(self, fname):
        self.fname = fname

    def __repr__(self):
        return json.dumps(self.__dict__)

หรือคลาสที่ดียิ่งกว่าคลาสย่อยของ FileItem จากJsonSerializableคลาส:

import json

class JsonSerializable(object):
    def toJson(self):
        return json.dumps(self.__dict__)

    def __repr__(self):
        return self.toJson()


class FileItem(JsonSerializable):
    def __init__(self, fname):
        self.fname = fname

การทดสอบ:

>>> f = FileItem('/foo/bar')
>>> f.toJson()
'{"fname": "/foo/bar"}'
>>> f
'{"fname": "/foo/bar"}'
>>> str(f) # string coercion
'{"fname": "/foo/bar"}'

2
สวัสดีฉันไม่ชอบวิธี "ตัวเข้ารหัสแบบกำหนดเอง" นี้จะดีกว่าถ้าคุณสามารถทำให้คลาสของคุณเป็น json ได้ ฉันลองและลองและลองและไม่มีอะไร มีความคิดวิธีการทำเช่นนี้ สิ่งคือโมดูล json ทดสอบคลาสของคุณกับชนิดของ python และยังบอกว่าสำหรับคลาสที่กำหนดเองทำให้ตัวเข้ารหัสของคุณ :) มันแกล้งทำหรือไม่? ดังนั้นฉันสามารถทำบางสิ่งบางอย่างในชั้นเรียนของฉันดังนั้นมันจึงทำตัวเหมือนรายการง่ายๆกับโมดูล json? ฉันลองsubclasscheckและinstancecheckแต่ไม่มีอะไร
Bojan Radojevic

@ADRENALIN คุณสามารถสืบทอดจากประเภทหลัก (อาจเป็น Dict) หากค่าแอตทริบิวต์คลาสทั้งหมดเป็นอนุกรมและคุณไม่สนใจแฮ็ค คุณยังสามารถใช้ jsonpickle หรือ json_tricks หรืออะไรก็ได้แทนที่จะเป็นแบบมาตรฐาน (ยังเป็นตัวเข้ารหัสที่กำหนดเอง แต่ไม่ใช่ตัวเข้ารหัสที่คุณต้องเขียนหรือโทร) ตัวอย่างของผักดองในอดีตนั้นจะเก็บไว้เป็น dict ของคุณสมบัติซึ่งคุณสามารถเปลี่ยนได้โดยการใช้__json__encode__/ __json_decode__(การเปิดเผย: ฉันทำอันสุดท้าย)
ทำเครื่องหมาย

30

เพียงเพิ่มto_jsonวิธีการในชั้นเรียนของคุณเช่นนี้:

def to_json(self):
  return self.message # or how you want it to be serialized

และเพิ่มรหัสนี้(จากคำตอบนี้ )ไปยังตำแหน่งที่ด้านบนของทุกสิ่ง:

from json import JSONEncoder

def _default(self, obj):
    return getattr(obj.__class__, "to_json", _default.default)(obj)

_default.default = JSONEncoder().default
JSONEncoder.default = _default

นี่จะเป็นโมดูลลิงแพทช์ลิงเมื่อมันถูกนำเข้าดังนั้น JSONEncoder.default () จะตรวจสอบวิธีการพิเศษ "to_json ()" โดยอัตโนมัติและใช้เพื่อเข้ารหัสวัตถุหากพบ

เช่นเดียวกับ Onur ที่พูด แต่ครั้งนี้คุณไม่จำเป็นต้องอัปเดตทุกรายการjson.dumps()ในโครงการของคุณ


6
ขอบคุณมาก ๆ! นี่เป็นคำตอบเดียวที่ทำให้ฉันสามารถทำสิ่งที่ฉันต้องการ: สามารถทำให้วัตถุเป็นอนุกรมโดยไม่ต้องเปลี่ยนรหัสที่มีอยู่ วิธีอื่น ๆ ส่วนใหญ่ไม่ได้ผลสำหรับฉัน วัตถุนั้นถูกกำหนดไว้ในห้องสมุดของบุคคลที่สามและรหัสซีเรียลไลเซชันก็เป็นของบุคคลที่สามด้วยเช่นกัน การเปลี่ยนพวกเขาจะอึดอัดใจ TheObject.to_json = my_serializerด้วยวิธีการของคุณผมจะต้องทำ
Yongwei Wu

24

ฉันเจอปัญหานี้เมื่อวันก่อนและติดตั้งเอนโค้ดเดอร์สำหรับออบเจ็กต์ Python ที่สามารถจัดการออบเจ็กต์ที่ซ้อนกันและฟิลด์ที่สืบทอดมา :

import json
import inspect

class ObjectEncoder(json.JSONEncoder):
    def default(self, obj):
        if hasattr(obj, "to_json"):
            return self.default(obj.to_json())
        elif hasattr(obj, "__dict__"):
            d = dict(
                (key, value)
                for key, value in inspect.getmembers(obj)
                if not key.startswith("__")
                and not inspect.isabstract(value)
                and not inspect.isbuiltin(value)
                and not inspect.isfunction(value)
                and not inspect.isgenerator(value)
                and not inspect.isgeneratorfunction(value)
                and not inspect.ismethod(value)
                and not inspect.ismethoddescriptor(value)
                and not inspect.isroutine(value)
            )
            return self.default(d)
        return obj

ตัวอย่าง:

class C(object):
    c = "NO"
    def to_json(self):
        return {"c": "YES"}

class B(object):
    b = "B"
    i = "I"
    def __init__(self, y):
        self.y = y

    def f(self):
        print "f"

class A(B):
    a = "A"
    def __init__(self):
        self.b = [{"ab": B("y")}]
        self.c = C()

print json.dumps(A(), cls=ObjectEncoder, indent=2, sort_keys=True)

ผลลัพธ์:

{
  "a": "A", 
  "b": [
    {
      "ab": {
        "b": "B", 
        "i": "I", 
        "y": "y"
      }
    }
  ], 
  "c": {
    "c": "YES"
  }, 
  "i": "I"
}

1
แม้ว่านี่จะค่อนข้างเก่า .. ฉันกำลังเผชิญกับข้อผิดพลาดในการนำเข้าแบบวงกลม ดังนั้นแทนที่จะในบรรทัดสุดท้ายที่ฉันทำอย่างนี้return obj return super(ObjectEncoder, self).default(obj)การอ้างอิงที่นี่
SomeTypeFoo

24

หากคุณกำลังใช้ Python3.5 + jsonsคุณสามารถใช้ มันจะแปลงวัตถุของคุณ (และคุณลักษณะทั้งหมดของมันซ้ำ) เพื่อ dict

import jsons

a_dict = jsons.dump(your_object)

หรือถ้าคุณต้องการสตริง:

a_str = jsons.dumps(your_object)

หรือถ้าชั้นเรียนของคุณดำเนินการjsons.JsonSerializable:

a_dict = your_object.json

3
ถ้าคุณมีความสามารถที่จะใช้งูหลาม 3.7+ ผมพบว่าวิธีการแก้ปัญหาที่สะอาดชั้นเรียนหลามแปลง dicts และสตริง JSON (และ viceversa) คือการผสมjsonsห้องสมุดที่มีdataclasses จนถึงตอนนี้ดีสำหรับฉัน!
Ruluk

3
นี่เป็นไลบรารีภายนอกที่ไม่ได้ติดตั้งในการติดตั้ง Python มาตรฐาน
Noumenon

เฉพาะคลาสที่มีช่องแอททริบิวต์เท่านั้น
yehudahs

คุณสามารถ แต่คุณไม่จำเป็นต้องใช้ช่อง เฉพาะเมื่อการทุ่มตลาดตามลายเซ็นของระดับเฉพาะคุณจะต้องมีช่อง ในรุ่นที่กำลังจะมาถึง 1.1.0 นั้นยังไม่มีอีกต่อไป
RH

11
import simplejson

class User(object):
    def __init__(self, name, mail):
        self.name = name
        self.mail = mail

    def _asdict(self):
        return self.__dict__

print(simplejson.dumps(User('alice', 'alice@mail.com')))

หากใช้มาตรฐานjsonคุณจำเป็นต้องกำหนดdefaultฟังก์ชั่น

import json
def default(o):
    return o._asdict()

print(json.dumps(User('alice', 'alice@mail.com'), default=default))

2
ฉันทำให้มันง่ายขึ้นโดยการลบฟังก์ชั่น _asdict ด้วยแลมบ์ดา json.dumps(User('alice', 'alice@mail.com'), default=lambda x: x.__dict__)
JustEngland

8

jsonมีข้อ จำกัด ในแง่ของวัตถุที่สามารถพิมพ์ได้และjsonpickle(คุณอาจต้องการpip install jsonpickle) มีข้อ จำกัด ในแง่ที่ไม่สามารถเยื้องข้อความได้ หากคุณต้องการตรวจสอบเนื้อหาของวัตถุที่คุณไม่สามารถเปลี่ยนชั้นเรียนได้ฉันก็ยังไม่พบวิธีที่ตรงกว่า:

 import json
 import jsonpickle
 ...
 print  json.dumps(json.loads(jsonpickle.encode(object)), indent=2)

หมายเหตุ: ยังคงไม่สามารถพิมพ์วิธีการวัตถุ


6

ชั้นนี้สามารถทำเคล็ดลับมันแปลงวัตถุเป็นมาตรฐาน json

import json


class Serializer(object):
    @staticmethod
    def serialize(object):
        return json.dumps(object, default=lambda o: o.__dict__.values()[0])

การใช้งาน:

Serializer.serialize(my_object)

ทำงานในและpython2.7python3


ฉันชอบวิธีนี้มากที่สุด ฉันพบปัญหาเมื่อพยายามทำให้อนุกรมวัตถุที่ซับซ้อนมากขึ้นซึ่งสมาชิก / วิธีการไม่ได้ต่อเนื่องกัน นี่คือการใช้งานของฉันที่ทำงานกับวัตถุมากขึ้น: `` `คลาส Serializer (วัตถุ): @ staticmethod def serialize (obj): def check (o): สำหรับ k, v ใน o .__ dict __. items (): ลอง: _ = json .dumps (v) o .__ dict __ [k] = v ยกเว้น TypeError: o .__ dict __ [k] = str (v) return o กลับ json.dumps (check (obj) .dict__, เยื้อง = 2) `` `
จะ ชาร์ลตัน

4
import json

class Foo(object):
    def __init__(self):
        self.bar = 'baz'
        self._qux = 'flub'

    def somemethod(self):
        pass

def default(instance):
    return {k: v
            for k, v in vars(instance).items()
            if not str(k).startswith('_')}

json_foo = json.dumps(Foo(), default=default)
assert '{"bar": "baz"}' == json_foo

print(json_foo)

จากdoc : พารามิเตอร์default(obj)คือฟังก์ชันที่ควรส่งคืน obj หรือยกระดับ TypeError ค่าเริ่มต้นdefaultเพียงแค่เพิ่ม TypeError
luckydonald

4

jaracoให้คำตอบที่เรียบร้อย ฉันต้องการแก้ไขสิ่งเล็กน้อย แต่ใช้งานได้:

รหัส

# Your custom class
class MyCustom(object):
    def __json__(self):
        return {
            'a': self.a,
            'b': self.b,
            '__python__': 'mymodule.submodule:MyCustom.from_json',
        }

    to_json = __json__  # supported by simplejson

    @classmethod
    def from_json(cls, json):
        obj = cls()
        obj.a = json['a']
        obj.b = json['b']
        return obj

# Dumping and loading
import simplejson

obj = MyCustom()
obj.a = 3
obj.b = 4

json = simplejson.dumps(obj, for_json=True)

# Two-step loading
obj2_dict = simplejson.loads(json)
obj2 = MyCustom.from_json(obj2_dict)

# Make sure we have the correct thing
assert isinstance(obj2, MyCustom)
assert obj2.__dict__ == obj.__dict__

โปรดทราบว่าเราต้องการสองขั้นตอนในการโหลด สำหรับตอนนี้__python__คุณสมบัติไม่ได้ใช้

มันเป็นเรื่องธรรมดาแค่ไหน?

ใช้วิธีการของAlJohriฉันตรวจสอบความนิยมของวิธีการ:

การทำให้เป็นอนุกรม (Python -> JSON):

  • to_json: 266,595 เมื่อวันที่ 2018-06-27
  • toJSON: 96,307 วันที่ 2018-06-27
  • __json__: 8,504 วันที่ 2018-06-27
  • for_json: 6,937 วันที่ 2018-06-27

การดีซีเรียลไลซ์เซชั่น (JSON -> Python):

  • from_json: 226,101 วันที่ 2018-06-27

4

สิ่งนี้ใช้ได้ดีสำหรับฉัน:

class JsonSerializable(object):

    def serialize(self):
        return json.dumps(self.__dict__)

    def __repr__(self):
        return self.serialize()

    @staticmethod
    def dumper(obj):
        if "serialize" in dir(obj):
            return obj.serialize()

        return obj.__dict__

แล้ว

class FileItem(JsonSerializable):
    ...

และ

log.debug(json.dumps(<my object>, default=JsonSerializable.dumper, indent=2))

3

หากคุณไม่คิดที่จะติดตั้งแพ็คเกจคุณสามารถใช้json-tricks :

pip install json-tricks

หลังจากนั้นคุณเพียงแค่ต้องนำเข้าdump(s)จากjson_tricksแทน json และโดยปกติจะใช้งานได้:

from json_tricks import dumps
json_str = dumps(cls_instance, indent=4)

ซึ่งจะให้

{
        "__instance_type__": [
                "module_name.test_class",
                "MyTestCls"
        ],
        "attributes": {
                "attr": "val",
                "dct_attr": {
                        "hello": 42
                }
        }
}

และนั่นเป็นพื้น!


โดยทั่วไปจะใช้งานได้ดี มีข้อยกเว้นบางประการเช่นหากมีสิ่งพิเศษเกิดขึ้น__new__หรือมีเวทมนตร์เวทมากกว่านี้เกิดขึ้น

เห็นได้ชัดว่าการโหลดยังใช้งานได้ (ไม่อย่างนั้นประเด็นคืออะไร):

from json_tricks import loads
json_str = loads(json_str)

สิ่งนี้จะถือว่าmodule_name.test_class.MyTestClsสามารถนำเข้าและไม่เปลี่ยนแปลงในวิธีที่เข้ากันไม่ได้ คุณจะได้รับอินสแตนซ์ไม่ใช่พจนานุกรมหรือบางอย่างและควรเป็นสำเนาที่เหมือนกันกับที่คุณทิ้ง

หากคุณต้องการปรับแต่งสิ่งที่ได้รับ (de) ต่อเนื่องกันคุณสามารถเพิ่มวิธีพิเศษให้กับชั้นเรียนของคุณเช่น:

class CustomEncodeCls:
        def __init__(self):
                self.relevant = 42
                self.irrelevant = 37

        def __json_encode__(self):
                # should return primitive, serializable types like dict, list, int, string, float...
                return {'relevant': self.relevant}

        def __json_decode__(self, **attrs):
                # should initialize all properties; note that __init__ is not called implicitly
                self.relevant = attrs['relevant']
                self.irrelevant = 12

ซึ่งซีเรียลไลซ์บางส่วนเท่านั้นของพารามิเตอร์คุณลักษณะเป็นตัวอย่าง

และเป็นโบนัสฟรีคุณจะได้รับ (de) อนุกรมของ numpy arrays วันที่ & เวลาสั่งแผนที่รวมถึงความสามารถในการรวมความคิดเห็นใน json

คำเตือน: ฉันสร้างjson_tricksเพราะฉันมีปัญหาเช่นเดียวกับคุณ


1
ฉันเพิ่งทดสอบ json_tricks และมันใช้งานได้สวยงาม (ในปี 2019)
pauljohn32

2

jsonweb น่าจะเป็นทางออกที่ดีที่สุดสำหรับฉัน ดูhttp://www.jsonweb.info/th/latest/

from jsonweb.encode import to_object, dumper

@to_object()
class DataModel(object):
  def __init__(self, id, value):
   self.id = id
   self.value = value

>>> data = DataModel(5, "foo")
>>> dumper(data)
'{"__type__": "DataModel", "id": 5, "value": "foo"}'

มันทำงานได้ดีสำหรับวัตถุที่ซ้อนกัน? รวมถึงการถอดรหัสและการเข้ารหัส
Simone Zandara

1

นี่คือ 3 เซ็นต์ของฉัน ...
นี่แสดงให้เห็นถึงการ json serialization ชัดเจนสำหรับวัตถุหลามเหมือนต้นไม้
หมายเหตุ: ถ้าคุณต้องการรหัสบางอย่างเช่นนี้คุณสามารถใช้คลาสFilePath ที่บิดได้

import json, sys, os

class File:
    def __init__(self, path):
        self.path = path

    def isdir(self):
        return os.path.isdir(self.path)

    def isfile(self):
        return os.path.isfile(self.path)

    def children(self):        
        return [File(os.path.join(self.path, f)) 
                for f in os.listdir(self.path)]

    def getsize(self):        
        return os.path.getsize(self.path)

    def getModificationTime(self):
        return os.path.getmtime(self.path)

def _default(o):
    d = {}
    d['path'] = o.path
    d['isFile'] = o.isfile()
    d['isDir'] = o.isdir()
    d['mtime'] = int(o.getModificationTime())
    d['size'] = o.getsize() if o.isfile() else 0
    if o.isdir(): d['children'] = o.children()
    return d

folder = os.path.abspath('.')
json.dump(File(folder), sys.stdout, default=_default)

1

ฉันวิ่งเข้าไปในปัญหานี้เมื่อฉันพยายามที่จะเก็บรูปแบบแคระเข้า JSONFieldPostgreSQL

หลังจากดิ้นรนไประยะหนึ่งแล้วนี่เป็นคำตอบทั่วไป

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

สมมติว่าคุณในปัจจุบันมีรูปแบบที่ประกอบด้วยบางฟิลด์ที่ไม่สามารถทำให้เป็นอนุกรมกับ JSON และรูปแบบที่มีฟิลด์ JSON นั้นมีลักษณะดังนี้:

class SomeClass(Model):
    json_field = JSONField()

เพียงกำหนดแบบกำหนดเองJSONEncoderดังนี้:

class CustomJsonEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, SomeTypeUnsupportedByJsonDumps):
            return < whatever value you want >
        return json.JSONEncoder.default(self, obj)

    @staticmethod
    def json_dumper(obj):
        return json.dumps(obj, cls=CustomJsonEncoder)

จากนั้นใช้งานในแบบที่คุณJSONFieldชอบด้านล่าง:

class SomeClass(Model):
    json_field = JSONField(dumps=CustomJsonEncoder.json_dumper)

ที่สำคัญคือdefault(self, obj)วิธีการข้างต้น สำหรับการ... is not JSON serializableร้องเรียนทุกครั้งที่คุณได้รับจาก Python เพียงเพิ่มรหัสเพื่อจัดการประเภท JSON ที่ไม่สามารถปรับเปลี่ยนได้ (เช่นEnumหรือdatetime)

ตัวอย่างเช่นนี่คือวิธีที่ฉันสนับสนุนคลาสที่สืบทอดจากEnum:

class TransactionType(Enum):
   CURRENT = 1
   STACKED = 2

   def default(self, obj):
       if isinstance(obj, TransactionType):
           return obj.value
       return json.JSONEncoder.default(self, obj)

ในที่สุดด้วยโค้ดที่นำมาใช้ด้านบนคุณสามารถแปลง Peewee รุ่นใด ๆ ให้เป็นวัตถุ JSON แบบปรับได้ดังนี้

peewee_model = WhateverPeeweeModel()
new_model = SomeClass()
new_model.json_field = model_to_dict(peewee_model)

แม้ว่าโค้ดข้างต้นนั้นค่อนข้างเจาะจงสำหรับ Peewee แต่ฉันคิดว่า:

  1. สามารถใช้ได้กับ ORM อื่น ๆ (Django ฯลฯ ) โดยทั่วไป
  2. นอกจากนี้หากคุณเข้าใจวิธีการjson.dumpsทำงานโซลูชันนี้ก็สามารถใช้ได้กับ Python (sans ORM) โดยทั่วไปเช่นกัน

คำถามใด ๆ โปรดโพสต์ในส่วนความเห็น ขอบคุณ!


1

ฟังก์ชันนี้ใช้การเรียกซ้ำเพื่อวนซ้ำในทุกส่วนของพจนานุกรมจากนั้นเรียกเมธอดrepr () ของคลาสที่ไม่ใช่ชนิดบิลด์อิน

def sterilize(obj):
    object_type = type(obj)
    if isinstance(obj, dict):
        return {k: sterilize(v) for k, v in obj.items()}
    elif object_type in (list, tuple):
        return [sterilize(v) for v in obj]
    elif object_type in (str, int, bool):
        return obj
    else:
        return obj.__repr__()


0

ฉันคิดวิธีแก้ปัญหาของตัวเองขึ้นมา ใช้วิธีนี้ส่งเอกสารใด ๆ ( dict , รายการ , ObjectId และอื่น ๆ ) เพื่อทำให้เป็นอนุกรม

def getSerializable(doc):
    # check if it's a list
    if isinstance(doc, list):
        for i, val in enumerate(doc):
            doc[i] = getSerializable(doc[i])
        return doc

    # check if it's a dict
    if isinstance(doc, dict):
        for key in doc.keys():
            doc[key] = getSerializable(doc[key])
        return doc

    # Process ObjectId
    if isinstance(doc, ObjectId):
        doc = str(doc)
        return doc

    # Use any other custom serializting stuff here...

    # For the rest of stuff
    return doc

0

ฉันเลือกที่จะใช้มัณฑนากรเพื่อแก้ปัญหาการทำให้เป็นอันดับวัตถุวันที่และเวลา นี่คือรหัสของฉัน:

#myjson.py
#Author: jmooremcc 7/16/2017

import json
from datetime import datetime, date, time, timedelta
"""
This module uses decorators to serialize date objects using json
The filename is myjson.py
In another module you simply add the following import statement:
    from myjson import json

json.dumps and json.dump will then correctly serialize datetime and date 
objects
"""

def json_serial(obj):
    """JSON serializer for objects not serializable by default json code"""

    if isinstance(obj, (datetime, date)):
        serial = str(obj)
        return serial
    raise TypeError ("Type %s not serializable" % type(obj))


def FixDumps(fn):
    def hook(obj):
        return fn(obj, default=json_serial)

    return hook

def FixDump(fn):
    def hook(obj, fp):
        return fn(obj,fp, default=json_serial)

    return hook


json.dumps=FixDumps(json.dumps)
json.dump=FixDump(json.dump)


if __name__=="__main__":
    today=datetime.now()
    data={'atime':today, 'greet':'Hello'}
    str=json.dumps(data)
    print str

ด้วยการนำเข้าโมดูลข้างต้นโมดูลอื่น ๆ ของฉันใช้ json ด้วยวิธีปกติ (โดยไม่ระบุคำหลักเริ่มต้น) เพื่อทำให้ข้อมูลเป็นอนุกรมที่มีวัตถุเวลาวันที่ รหัส serializer ของวันที่และเวลาจะเรียกโดยอัตโนมัติสำหรับ json.dumps และ json.dump


0

ฉันชอบวิธีของ Lost Koder มากที่สุด ฉันพบปัญหาเมื่อพยายามทำให้อนุกรมวัตถุที่ซับซ้อนมากขึ้นซึ่งสมาชิก / วิธีการไม่ได้ต่อเนื่องกัน นี่คือการใช้งานของฉันที่ทำงานกับวัตถุมากขึ้น:

class Serializer(object):
    @staticmethod
    def serialize(obj):
        def check(o):
            for k, v in o.__dict__.items():
                try:
                    _ = json.dumps(v)
                    o.__dict__[k] = v
                except TypeError:
                    o.__dict__[k] = str(v)
            return o
        return json.dumps(check(obj).__dict__, indent=2)

0

หากคุณสามารถติดตั้งแพคเกจฉันขอแนะนำให้ลองดิลซึ่งทำงานได้ดีสำหรับโครงการของฉัน สิ่งที่ดีเกี่ยวกับแพคเกจนี้คือมันมีอินเทอร์เฟซแบบเดียวกันpickleดังนั้นถ้าคุณได้ใช้pickleในโครงการของคุณแล้วคุณสามารถแทนที่dillและดูว่าสคริปต์ทำงานหรือไม่โดยไม่ต้องเปลี่ยนรหัสใด ๆ ดังนั้นจึงเป็นทางออกที่ถูกมากที่จะลอง!

(ต่อต้านการเปิดเผยข้อมูลทั้งหมด: ฉันไม่มีส่วนเกี่ยวข้องกับและไม่เคยมีส่วนร่วมในโครงการผักชีฝรั่ง)

ติดตั้งแพ็คเกจ:

pip install dill

จากนั้นแก้ไขรหัสของคุณเพื่อนำเข้าdillแทนpickle:

# import pickle
import dill as pickle

เรียกใช้สคริปต์ของคุณและดูว่าทำงานได้หรือไม่ (ถ้าเป็นเช่นนั้นคุณอาจต้องการล้างรหัสของคุณเพื่อไม่ให้แชโดว์pickleชื่อโมดูลหายไปอีกต่อไป!)

ข้อมูลเฉพาะบางอย่างเกี่ยวกับประเภทข้อมูลที่dillสามารถและไม่สามารถทำให้เป็นอันดับจากหน้าโครงการ :

dill สามารถดองประเภทมาตรฐานต่อไปนี้:

none, ชนิด, บูล, int, long, float, complex, str, unicode, tuple, ลิสต์, dict, ไฟล์, บัฟเฟอร์, builtin, คลาสสไตล์ทั้งเก่าและใหม่, อินสแตนซ์ของคลาสสไตล์เก่าและใหม่, ตั้งค่า, อาร์เรย์ ฟังก์ชั่นข้อยกเว้น

dill นอกจากนี้ยังสามารถดองประเภทมาตรฐานที่แปลกใหม่มากขึ้น:

ฟังก์ชั่นที่มีอัตราผลตอบแทน, ฟังก์ชั่นที่ซ้อนกัน, lambdas, เซลล์, วิธี, unboundmethod, โมดูล, รหัส, methodwrapper, dictproxy, methoddescriptor, getsetdescriptor, memberdescriptor, wrapperdescriptor, xrange, slice, ไม่ได้ดำเนินการ, จุดไข่ปลา

dill ยังไม่สามารถดองประเภทมาตรฐานเหล่านี้ได้:

เฟรมเครื่องกำเนิดไฟฟ้าย้อนกลับ


0

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

https://gist.github.com/andy-d/b7878d0044a4242c0498ed6d67fd50fe


0

ในการเพิ่มตัวเลือกอื่น: คุณสามารถใช้attrsแพ็คเกจและasdictวิธีการได้

class ObjectEncoder(JSONEncoder):
    def default(self, o):
        return attr.asdict(o)

json.dumps(objects, cls=ObjectEncoder)

และเพื่อแปลงกลับ

def from_json(o):
    if '_obj_name' in o:
        type_ = o['_obj_name']
        del o['_obj_name']
        return globals()[type_](**o)
    else:
        return o

data = JSONDecoder(object_hook=from_json).decode(data)

ชั้นเรียนมีลักษณะเช่นนี้

@attr.s
class Foo(object):
    x = attr.ib()
    _obj_name = attr.ib(init=False, default='Foo')

0

นอกเหนือจากคำตอบของ Onurคุณอาจต้องการจัดการกับ datetime ประเภทดังนี้
(เพื่อจัดการ: วัตถุ 'datetime.datetime' ไม่มีแอตทริบิวต์ ' dict ' ยกเว้น)

def datetime_option(value):
    if isinstance(value, datetime.date):
        return value.timestamp()
    else:
        return value.__dict__

การใช้งาน:

def toJSON(self):
    return json.dumps(self, default=datetime_option, sort_keys=True, indent=4)

0

ก่อนอื่นเราต้องทำให้อ็อพเจ็กต์ JSON ของเราสอดคล้องกันดังนั้นเราสามารถดัมพ์โดยใช้โมดูล JSON มาตรฐาน ฉันทำอย่างนี้:

def serialize(o):
    if isinstance(o, dict):
        return {k:serialize(v) for k,v in o.items()}
    if isinstance(o, list):
        return [serialize(e) for e in o]
    if isinstance(o, bytes):
        return o.decode("utf-8")
    return o

0

การสร้างคำตอบของQuinten Cabo :

def sterilize(obj):
    if type(obj) in (str, float, int, bool, type(None)):
        return obj
    elif isinstance(obj, dict):
        return {k: sterilize(v) for k, v in obj.items()}
    elif hasattr(obj, '__iter__') and callable(obj.__iter__):
        return [sterilize(v) for v in obj]
    elif hasattr(obj, '__dict__'):
        return {k: sterilize(v) for k, v in obj.__dict__.items() if k not in ['__module__', '__dict__', '__weakref__', '__doc__']}
    else:
        return repr(obj)

ความแตกต่างคือ

  1. ใช้งานได้กับ iterable ใด ๆ แทน just listและtuple(ใช้ได้กับ NumPy arrays เป็นต้น)
  2. ใช้งานได้กับประเภทไดนามิก (ประเภทที่มี__dict__)
  3. รวมประเภทดั้งเดิมfloatและNoneเพื่อไม่ให้แปลงเป็นสตริง

เหลือไว้เป็นแบบฝึกหัดให้กับผู้อ่านคือการจัดการ__slots__เรียนที่มีทั้ง iterable และมีสมาชิกชั้นเรียนที่เป็นพจนานุกรมและยังมีสมาชิก ฯลฯ

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