JSONへのクラスインスタンスのシリアル化


186

クラスインスタンスのJSON文字列表現を作成しようとしていますが、問題があります。クラスが次のように構築されているとしましょう:

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

json.dumpsの呼び出しは次のようになります。

t = testclass()
json.dumps(t)

これは失敗し、testclassはJSONシリアライズ可能ではないことを教えてくれます。

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

私はpickleモジュールも使ってみました:

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

また、クラスインスタンス情報を提供しますが、クラスインスタンスのシリアル化されたコンテンツは提供しません。

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

何が悪いのですか?



30
1行を、使用するs = json.dumps(obj, default=lambda x: x.__dict__)直列化オブジェクトのインスタンス変数に、( self.value1self.value2、...)。その最も単純で最も簡単な方法。ネストされたオブジェクト構造をシリアル化します。このdefault関数は、特定のオブジェクトが直接シリアル化できない場合に呼び出されます。以下の私の回答もご覧ください。人気のある回答が不必要に複雑であることがわかりました。
codeman48 2017

1
あなたはtestclass何もありません__init__()(メソッドを、そのすべてのインスタンスは、同じ2つのクラス属性を共有することになりますvalue1し、value2、クラスステートメントで定義します)。クラスとインスタンスの違いを理解していますか?
martineau

1
このためのPythonライブラリはありgithub.com/jsonpickle/jsonpickleは(答えはスレッドに過ぎ以下であると、到達可能で文句を言わないので、コメントする。)
最高の願い

回答:


238

基本的な問題は、JSONエンコーダーjson.dumps()がデフォルトですべての組み込み型のオブジェクト型の限られたセットをシリアル化する方法しか認識しないことです。ここにリスト:https : //docs.python.org/3.3/library/json.html#encoders-and-decoders

良い解決策の1つは、クラスから関数を継承しJSONEncoderて実装し、JSONEncoder.default()その関数がクラスに適切なJSONを出力するようにすることです。

簡単な解決策は、呼び出すことであろうjson.dumps().__dict__、そのインスタンスのメンバー。これは標準のPythonでdictあり、クラスが単純な場合、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}

上記のアプローチについては、このブログ投稿で説明しています。

    __dict__を使用して任意のPythonオブジェクトをJSONにシリアル化する


3
私はこれを試しました。json.dumps(t .__ dict__)の呼び出しの最終結果はちょうど{}です。
フェルハン

6
これは、クラスに.__init__()メソッド関数がないため、クラスインスタンスに空の辞書があるためです。つまり、{}はサンプルコードの正しい結果です。
steveha

3
ありがとう。これでうまくいきます。パラメータなしの単純な初期化を追加し、json.dumps(t .__ dict__)を呼び出すと、次の形式で適切なデータが返されます:{"value2": "345"、 "value1": "123"}以前は、メンバーにカスタムシリアライザーが必要かどうか、initが必要かどうかが明示的に言及されていなかったか、見逃していたかがわかりませんでした。ありがとうございました。
フェルハン

3
これは単一のクラスでは機能しますが、関連するクラスの
オブジェクトでは機能し

2
@NwawelAIroume:はい。たとえばリストに複数のオブジェクトが含まれているオブジェクトがある場合、エラーは引き続き発生しますis not JSON serializable
gies0r

57

あなたが試すことができる私にとって素晴らしい方法が1つあります:

json.dumps()オプションのパラメーターのデフォルトを使用して、不明なタイプのカスタムシリアライザー関数を指定できます。

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__

最初の2つのifは日付と時刻のシリアル化obj.__dict__用であり、他のオブジェクトの場合は返されます。

最終的な呼び出しは次のようになります。

json.dumps(myObj, default=serialize)

コレクションをシリアル化していて、呼び出したくない場合に特に便利です。 __dict__、すべてのオブジェクトに対して明示的。ここで自動的に行われます。

これまでのところ、あなたの考えを楽しみにしてとてもうまくいきました。


わかりますNameError: name 'serialize' is not defined。任意のヒント?
カイルデラニー

非常に素晴らしい。スロットがあるクラスの場合のみ:try: dict = obj.__dict__ except AttributeError: dict = {s: getattr(obj, s) for s in obj.__slots__ if hasattr(obj, s)} return dict
空想

そのような一般的な言語には、オブジェクトをjsoninyするためのライナーがありません。静的に型付けされていないためです。
TheRennen

49

関数でdefault名前付きパラメーターを指定できますjson.dumps()

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

説明:

ドキュメント(フォーム2.73.6)。

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

(Python 2.7およびPython 3.xで動作します)

注:この場合、質問の例で試みているように、instance変数ではなくclass変数が必要です。(私は質問者がclass instance者がクラスのオブジェクトであるをしています)

私はこれを@phihagのここの答えから最初に学びました。それが仕事をするための最もシンプルでクリーンな方法であることがわかりました。


6
これは私にとってはうまくいきましたが、datetime.dateのメンバーのために、少し変更しました:default=lambda x: getattr(x, '__dict__', str(x))
Dakota Hawkins

@ダコタ素敵な回避策。datetime.dateC実装であるため、__dict__属性はありません。均一性のために私見はdatetime.dateそれを持っているべきです...
codeman48

22

私はただします:

data=json.dumps(myobject.__dict__)

これは完全な答えではありません。ある種の複雑なオブジェクトクラスがある場合、確実にすべてを取得することはできません。しかし、私はこれを私の単純なオブジェクトのいくつかに使用します。

それが本当にうまく機能するのは、OptionParserモジュールから取得する「オプション」クラスです。ここでは、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)

これをクラス内で使用していない場合は、自分自身を削除することをお勧めします。
SpiRail 2013

3
オブジェクトが他のオブジェクトで構成されていない限り、問題はありません。
Haroldo_OK 2014年


5

JSONは、実際には任意のPythonオブジェクトをシリアル化するためのものではありません。dictオブジェクトのシリアル化には最適ですが、pickleモジュールは実際に一般的に使用する必要があるものです。からの出力pickleは実際には人間が読めるものではありませんが、問題なくアンピクルできます。JSONの使用を主張する場合は、jsonpickle、興味深いハイブリッドアプローチであるモジュールを。

https://github.com/jsonpickle/jsonpickle


9
私がpickleで見る主な問題は、それがPython固有の形式であるのに対し、JSONはプラットフォームに依存しない形式であることです。JSONは、Webアプリケーションまたはモバイルアプリケーションのバックエンドを作成する場合に特に便利です。jsonpickleを指摘してくれてありがとう。
Haroldo_OK 2014年

@Haroldo_OK jsonpickleはまだJSONにエクスポートしていませんか?
Caelum 2015年

4

洗練されていないクラスをシリアル化するための2つの単純な関数を以下に示します。

コードを調整せずにクラスに新しいメンバーを追加できるため、これを構成タイプのものに使用します。

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

これを始める方法についていくつかの良い答えがあります。ただし、次の点に注意してください。

  • インスタンスが大きなデータ構造内にネストされている場合はどうなりますか?
  • クラス名も必要な場合はどうなりますか?
  • インスタンスを逆シリアル化する場合はどうなりますか?
  • __slots__代わりに使用している場合はどうなります__dict__か?
  • 自分でやりたくない場合はどうしますか?

json-tricksは、かなり長い間これを行うことができた(私が作成したものや他の人が作成した)ライブラリです。例えば:

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)

インスタンスを元に戻します。ここで、jsonは次のようになります。

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

独自のソリューションを作成する場合は、json-tricksいくつかの特別なケース(など__slots__)を忘れないようにソースを確認することができます。

また、numpy配列、日時、複素数などの他の型も実行します。コメントも可能です。


3

Python3.x

私の知識で到達できる最高のアプローチはこれでした。
このコードはset()も扱うことに注意してください。
このアプローチは、(2番目の例で)クラスの拡張が必要な​​だけの一般的なものです。
ファイルに対して行っているだけですが、動作を好みに合わせて変更するのは簡単です。

ただし、これはCoDecです。

もう少し作業を行うと、他の方法でクラスを構築できます。それをインスタンス化するデフォルトのコンストラクターを想定し、クラスdictを更新します。

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)

編集する

いくつかの調査により、メタクラスを使用して、SUPERCLASS registerメソッド呼び出しを必要とせずに一般化する方法を見つけました

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

受け入れられた回答で提案されている継承の代わりに、ポリモーフィズムを使用する方が良いと思います。それ以外の場合は、すべてのオブジェクトのエンコーディングをカスタマイズするための大きなif elseステートメントが必要です。つまり、JSONの一般的なデフォルトエンコーダーを次のように作成します。

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

次にjsonEnc()、シリアル化する各クラスに関数を含めます。例えば

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

それからあなたは電話します json.dumps(classInstance,default=jsonDefEncoder)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.