Tuần tự hóa cá thể lớp thành JSON


185

Tôi đang cố gắng tạo một biểu diễn chuỗi JSON của một thể hiện của lớp và gặp khó khăn. Giả sử lớp được xây dựng như thế này:

class testclass:
    value1 = "a"
    value2 = "b"

Một cuộc gọi đến json.dumps được thực hiện như thế này:

t = testclass()
json.dumps(t)

Đó là thất bại và nói với tôi rằng lớp thử nghiệm không phải là tuần tự hóa JSON.

TypeError: <__main__.testclass object at 0x000000000227A400> is not JSON serializable

Tôi cũng đã thử sử dụng mô-đun dưa chua:

t = testclass()
print(pickle.dumps(t, pickle.HIGHEST_PROTOCOL))

Và nó cung cấp thông tin cá thể lớp nhưng không phải là một nội dung nối tiếp của thể hiện lớp.

b'\x80\x03c__main__\ntestclass\nq\x00)\x81q\x01}q\x02b.'

Tôi đang làm gì sai?



30
Sử dụng một dòng, s = json.dumps(obj, default=lambda x: x.__dict__)để biến dụ serialize đối tượng ( self.value1, self.value2, ...). Đó là cách đơn giản nhất và thẳng nhất về phía trước. Nó sẽ tuần tự hóa các cấu trúc đối tượng lồng nhau. Các defaulthàm được gọi khi bất kỳ đối tượng nhất định là không trực tiếp serializable. Bạn cũng có thể nhìn vào câu trả lời của tôi dưới đây. Tôi tìm thấy những câu trả lời phổ biến phức tạp không cần thiết, có lẽ đúng trong một thời gian dài trước đây.
codeman48

1
Của bạn testclasskhông có __init__()phương thức, vì vậy tất cả các thể hiện sẽ chia sẻ cùng hai thuộc tính lớp ( value1value2) được xác định trong câu lệnh lớp. Bạn có hiểu sự khác biệt giữa một lớp và một thể hiện của một lớp không?
martineau

1
Có một thư viện python cho github.com/jsonpickle/jsonpickle này (nhận xét vì câu trả lời quá bên dưới chủ đề và sẽ không thể truy cập được.)
lời chúc tốt nhất

Câu trả lời:


237

Vấn đề cơ bản là bộ mã hóa JSON json.dumps()chỉ biết cách tuần tự hóa một tập hợp các loại đối tượng giới hạn theo mặc định, tất cả các kiểu dựng sẵn. Danh sách tại đây: https://docs.python.org/3.3/l Library / json.html # encoders-and-decoders

Một giải pháp tốt sẽ là làm cho lớp của bạn kế thừa từ JSONEncoderđó và sau đó thực hiện JSONEncoder.default()hàm và làm cho hàm đó phát ra JSON chính xác cho lớp của bạn.

Một giải pháp đơn giản sẽ được gọi json.dumps()vào .__dict__thành viên của trường hợp đó. Đó là một Python chuẩn dictvà nếu lớp của bạn đơn giản, nó sẽ là tuần tự hóa JSON.

class Foo(object):
    def __init__(self):
        self.x = 1
        self.y = 2

foo = Foo()
s = json.dumps(foo) # raises TypeError with "is not JSON serializable"

s = json.dumps(foo.__dict__) # s set to: {"x":1, "y":2}

Cách tiếp cận trên được thảo luận trong bài đăng blog này:

    Tuần tự hóa các đối tượng Python tùy ý sang JSON bằng cách sử dụng __dict__


3
Tôi đã thử điều này. Kết quả cuối cùng của một cuộc gọi đến json.dumps (t .__ dict__) chỉ là {}.
Ferhan

6
Đó là bởi vì lớp của bạn không có .__init__()hàm phương thức, nên thể hiện lớp của bạn có một từ điển trống. Nói cách khác, {}là kết quả chính xác cho mã ví dụ của bạn.
steveha

3
Cảm ơn. Đây là mẹo. Tôi đã thêm một init đơn giản không có tham số và bây giờ gọi json.dumps (t .__ dict__) trả về dữ liệu phù hợp ở định dạng: {"value2": "345", "value1": "123"} Tôi đã thấy các bài đăng như điều này trước đây, không chắc chắn liệu tôi có cần một bộ nối tiếp tùy chỉnh cho các thành viên hay không, cần init không được đề cập rõ ràng hay tôi đã bỏ lỡ nó. Cảm ơn bạn.
Ferhan

3
Công việc này dành cho một lớp duy nhất nhưng không phải với các lớp liên quan phản đối
Nwawel A Iroume

2
@NwawelAIroume: Đúng. Nếu bạn có một đối tượng, ví dụ như chứa nhiều đối tượng trong danh sách thì lỗi vẫn làis not JSON serializable
gies0r

57

Có một cách phù hợp với tôi mà bạn có thể thử:

json.dumps()có thể lấy mặc định tham số tùy chọn trong đó bạn có thể chỉ định chức năng tuần tự hóa tùy chỉnh cho các loại không xác định, trong trường hợp của tôi trông giống như

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

    if isinstance(obj, date):
        serial = obj.isoformat()
        return serial

    if isinstance(obj, time):
        serial = obj.isoformat()
        return serial

    return obj.__dict__

Hai if đầu tiên là cho tuần tự hóa ngày và thời gian và sau đó có một obj.__dict__trả lại cho bất kỳ đối tượng khác.

cuộc gọi cuối cùng trông giống như:

json.dumps(myObj, default=serialize)

Điều này đặc biệt tốt khi bạn sắp xếp tuần tự một bộ sưu tập và bạn không muốn gọi __dict__rõ ràng cho mọi đối tượng. Ở đây nó được thực hiện cho bạn tự động.

Cho đến nay làm việc rất tốt cho tôi, mong chờ những suy nghĩ của bạn.


Tôi nhận được NameError: name 'serialize' is not defined. Bất cứ lời khuyên?
Kyle Delaney

Rất đẹp. Chỉ dành cho các lớp có vị trí:try: dict = obj.__dict__ except AttributeError: dict = {s: getattr(obj, s) for s in obj.__slots__ if hasattr(obj, s)} return dict
fantastory

Thật đáng kinh ngạc khi một ngôn ngữ phổ biến như vậy không có ai có thể hiểu được một đối tượng. Phải bởi vì nó không được gõ tĩnh.
TheRennen

48

Bạn có thể chỉ định defaulttham số được đặt tên trong json.dumps()hàm:

json.dumps(obj, default=lambda x: x.__dict__)

Giải trình:

Hình thành các tài liệu ( 2.7 , 3.6 ):

``default(obj)`` is a function that should return a serializable version
of obj or raise TypeError. The default simply raises TypeError.

(Hoạt động trên Python 2.7 và Python 3.x)

Lưu ý: Trong trường hợp này, bạn cần instancecác biến và không phải classcác biến, như ví dụ trong câu hỏi cố gắng thực hiện. (Tôi giả sử người hỏi có nghĩa class instancelà một đối tượng của một lớp)

Tôi đã học được điều này đầu tiên từ câu trả lời của @ phihag ở đây . Tìm thấy nó là cách đơn giản nhất và sạch nhất để thực hiện công việc.


6
Điều này làm việc cho tôi, nhưng vì các thành viên datetime.date, tôi đã thay đổi nó một chút:default=lambda x: getattr(x, '__dict__', str(x))
Dakota Hawkins

@Dakota làm việc tốt xung quanh; datetime.datelà một triển khai C do đó nó không có __dict__thuộc tính. IMHO vì sự đồng nhất, datetime.datenên có nó ...
codeman48

22

Tôi vừa làm:

data=json.dumps(myobject.__dict__)

Đây không phải là câu trả lời đầy đủ và nếu bạn có một số loại đối tượng phức tạp, bạn chắc chắn sẽ không nhận được mọi thứ. Tuy nhiên tôi sử dụng điều này cho một số đối tượng đơn giản của tôi.

Một thứ mà nó hoạt động thực sự tốt là lớp "tùy chọn" mà bạn nhận được từ mô-đun OptionParser. Đây là cùng với yêu cầu JSON.

  def executeJson(self, url, options):
        data=json.dumps(options.__dict__)
        if options.verbose:
            print data
        headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
        return requests.post(url, data, headers=headers)

Bạn có thể muốn tự xóa, nếu bạn không sử dụng cái này trong một lớp.
SpiRail

3
Điều đó sẽ hoạt động tốt, miễn là đối tượng không bao gồm các đối tượng khác.
Haroldo_OK

19

Sử dụng jsonpickle

import jsonpickle

object = YourClass()
json_object = jsonpickle.encode(object)

5

JSON không thực sự có nghĩa là để tuần tự hóa các đối tượng Python tùy ý. Thật tuyệt vời khi tuần tự hóa dictcác đối tượng, nhưng picklemô-đun thực sự là những gì bạn nên sử dụng nói chung. Đầu ra từ picklekhông thực sự có thể đọc được, nhưng nó sẽ giải nén tốt. Nếu bạn khăng khăng sử dụng JSON, bạn có thể kiểm tra jsonpicklemô-đun, đây là một cách tiếp cận lai thú vị.

https://github.com/jsonpickle/jsonpickle


9
Vấn đề chính tôi thấy với dưa chua là đó là định dạng dành riêng cho Python, trong khi JSON là định dạng độc lập với nền tảng. JSON đặc biệt hữu ích nếu bạn đang viết một ứng dụng web hoặc phụ trợ cho một số ứng dụng di động. Điều đó đã được nói, cảm ơn vì đã chỉ ra jsonpickle.
Haroldo_OK

@Haroldo_OK Không jsonpickle vẫn xuất sang JSON, chỉ là không thể đọc được cho con người?
Caelum

4

Dưới đây là hai hàm đơn giản để tuần tự hóa bất kỳ lớp không phức tạp nào, không có gì lạ mắt như được giải thích trước đây.

Tôi sử dụng điều này cho các loại cấu hình bởi vì tôi có thể thêm thành viên mới vào các lớp mà không cần điều chỉnh mã.

import json

class SimpleClass:
    def __init__(self, a=None, b=None, c=None):
        self.a = a
        self.b = b
        self.c = c

def serialize_json(instance=None, path=None):
    dt = {}
    dt.update(vars(instance))

    with open(path, "w") as file:
        json.dump(dt, file)

def deserialize_json(cls=None, path=None):
    def read_json(_path):
        with open(_path, "r") as file:
            return json.load(file)

    data = read_json(path)

    instance = object.__new__(cls)

    for key, value in data.items():
        setattr(instance, key, value)

    return instance

# Usage: Create class and serialize under Windows file system.
write_settings = SimpleClass(a=1, b=2, c=3)
serialize_json(write_settings, r"c:\temp\test.json")

# Read back and rehydrate.
read_settings = deserialize_json(SimpleClass, r"c:\temp\test.json")

# results are the same.
print(vars(write_settings))
print(vars(read_settings))

# output:
# {'c': 3, 'b': 2, 'a': 1}
# {'c': 3, 'b': 2, 'a': 1}

3

Có một số câu trả lời tốt về cách bắt đầu làm điều này. Nhưng có một số điều cần lưu ý:

  • Điều gì xảy ra nếu thể hiện được lồng trong một cấu trúc dữ liệu lớn?
  • Điều gì nếu cũng muốn tên lớp?
  • Điều gì sẽ xảy ra nếu bạn muốn giải tuần tự hóa?
  • Nếu bạn đang sử dụng __slots__thay vì __dict__thì sao?
  • Điều gì nếu bạn không muốn tự làm điều đó?

json-trick là một thư viện (mà tôi đã tạo ra và những người khác đã đóng góp) đã có thể làm điều này trong một thời gian dài. Ví dụ:

class MyTestCls:
    def __init__(self, **kwargs):
        for k, v in kwargs.items():
            setattr(self, k, v)

cls_instance = MyTestCls(s='ub', dct={'7': 7})

json = dumps(cls_instance, indent=4)
instance = loads(json)

Bạn sẽ lấy lại ví dụ của bạn. Ở đây json trông như thế này:

{
    "__instance_type__": [
        "json_tricks.test_class",
        "MyTestCls"
    ],
    "attributes": {
        "s": "ub",
        "dct": {
            "7": 7
        }
    }
}

Nếu bạn muốn đưa ra giải pháp của riêng mình, bạn có thể xem xét nguồn gốc json-tricksđể không quên một số trường hợp đặc biệt (như __slots__).

Nó cũng thực hiện các loại khác như mảng numpy, datetimes, số phức; nó cũng cho phép bình luận


3

Python3.x

Aproach tốt nhất tôi có thể đạt được với kiến ​​thức của mình là điều này.
Lưu ý rằng mã này xử lý set () quá.
Cách tiếp cận này là chung chung chỉ cần mở rộng lớp (trong ví dụ thứ hai).
Lưu ý rằng tôi chỉ làm việc đó với các tệp, nhưng thật dễ dàng để sửa đổi hành vi theo sở thích của bạn.

Tuy nhiên đây là một CoDec.

Với một chút công việc hơn, bạn có thể xây dựng lớp của mình theo những cách khác. Tôi giả sử một hàm tạo mặc định để thể hiện nó, sau đó tôi cập nhật dict lớp.

import json
import collections


class JsonClassSerializable(json.JSONEncoder):

    REGISTERED_CLASS = {}

    def register(ctype):
        JsonClassSerializable.REGISTERED_CLASS[ctype.__name__] = ctype

    def default(self, obj):
        if isinstance(obj, collections.Set):
            return dict(_set_object=list(obj))
        if isinstance(obj, JsonClassSerializable):
            jclass = {}
            jclass["name"] = type(obj).__name__
            jclass["dict"] = obj.__dict__
            return dict(_class_object=jclass)
        else:
            return json.JSONEncoder.default(self, obj)

    def json_to_class(self, dct):
        if '_set_object' in dct:
            return set(dct['_set_object'])
        elif '_class_object' in dct:
            cclass = dct['_class_object']
            cclass_name = cclass["name"]
            if cclass_name not in self.REGISTERED_CLASS:
                raise RuntimeError(
                    "Class {} not registered in JSON Parser"
                    .format(cclass["name"])
                )
            instance = self.REGISTERED_CLASS[cclass_name]()
            instance.__dict__ = cclass["dict"]
            return instance
        return dct

    def encode_(self, file):
        with open(file, 'w') as outfile:
            json.dump(
                self.__dict__, outfile,
                cls=JsonClassSerializable,
                indent=4,
                sort_keys=True
            )

    def decode_(self, file):
        try:
            with open(file, 'r') as infile:
                self.__dict__ = json.load(
                    infile,
                    object_hook=self.json_to_class
                )
        except FileNotFoundError:
            print("Persistence load failed "
                  "'{}' do not exists".format(file)
                  )


class C(JsonClassSerializable):

    def __init__(self):
        self.mill = "s"


JsonClassSerializable.register(C)


class B(JsonClassSerializable):

    def __init__(self):
        self.a = 1230
        self.c = C()


JsonClassSerializable.register(B)


class A(JsonClassSerializable):

    def __init__(self):
        self.a = 1
        self.b = {1, 2}
        self.c = B()

JsonClassSerializable.register(A)

A().encode_("test")
b = A()
b.decode_("test")
print(b.a)
print(b.b)
print(b.c.a)

Biên tập

Với một số nghiên cứu nữa, tôi đã tìm ra một cách để khái quát hóa mà không cần phải gọi phương thức đăng ký SUPERCLASS , sử dụng siêu dữ liệu

import json
import collections

REGISTERED_CLASS = {}

class MetaSerializable(type):

    def __call__(cls, *args, **kwargs):
        if cls.__name__ not in REGISTERED_CLASS:
            REGISTERED_CLASS[cls.__name__] = cls
        return super(MetaSerializable, cls).__call__(*args, **kwargs)


class JsonClassSerializable(json.JSONEncoder, metaclass=MetaSerializable):

    def default(self, obj):
        if isinstance(obj, collections.Set):
            return dict(_set_object=list(obj))
        if isinstance(obj, JsonClassSerializable):
            jclass = {}
            jclass["name"] = type(obj).__name__
            jclass["dict"] = obj.__dict__
            return dict(_class_object=jclass)
        else:
            return json.JSONEncoder.default(self, obj)

    def json_to_class(self, dct):
        if '_set_object' in dct:
            return set(dct['_set_object'])
        elif '_class_object' in dct:
            cclass = dct['_class_object']
            cclass_name = cclass["name"]
            if cclass_name not in REGISTERED_CLASS:
                raise RuntimeError(
                    "Class {} not registered in JSON Parser"
                    .format(cclass["name"])
                )
            instance = REGISTERED_CLASS[cclass_name]()
            instance.__dict__ = cclass["dict"]
            return instance
        return dct

    def encode_(self, file):
        with open(file, 'w') as outfile:
            json.dump(
                self.__dict__, outfile,
                cls=JsonClassSerializable,
                indent=4,
                sort_keys=True
            )

    def decode_(self, file):
        try:
            with open(file, 'r') as infile:
                self.__dict__ = json.load(
                    infile,
                    object_hook=self.json_to_class
                )
        except FileNotFoundError:
            print("Persistence load failed "
                  "'{}' do not exists".format(file)
                  )


class C(JsonClassSerializable):

    def __init__(self):
        self.mill = "s"


class B(JsonClassSerializable):

    def __init__(self):
        self.a = 1230
        self.c = C()


class A(JsonClassSerializable):

    def __init__(self):
        self.a = 1
        self.b = {1, 2}
        self.c = B()


A().encode_("test")
b = A()
b.decode_("test")
print(b.a)
# 1
print(b.b)
# {1, 2}
print(b.c.a)
# 1230
print(b.c.c.mill)
# s

2

Tôi tin thay vì thừa kế như đề xuất trong câu trả lời được chấp nhận, tốt hơn là sử dụng đa hình. Nếu không, bạn phải có một câu lệnh lớn nếu khác để tùy chỉnh mã hóa của mọi đối tượng. Điều đó có nghĩa là tạo một bộ mã hóa mặc định chung cho JSON như:

def jsonDefEncoder(obj):
   if hasattr(obj, 'jsonEnc'):
      return obj.jsonEnc()
   else: #some default behavior
      return obj.__dict__

và sau đó có một jsonEnc()chức năng trong mỗi lớp bạn muốn tuần tự hóa. ví dụ

class A(object):
   def __init__(self,lengthInFeet):
      self.lengthInFeet=lengthInFeet
   def jsonEnc(self):
      return {'lengthInMeters': lengthInFeet * 0.3 } # each foot is 0.3 meter

Sau đó, bạn gọi json.dumps(classInstance,default=jsonDefEncoder)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.