web-dev-qa-db-ja.com

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

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

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

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

t = testclass()
json.dumps(t)

テストクラスが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.'

何がおかしいのですか?

146
ferhan

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

1つの良い解決策は、クラスにJSONEncoderを継承させてからJSONEncoder.default()関数を実装し、その関数にクラスの正しいJSONを発行させることです。

簡単な解決策は、そのインスタンスの.__dict__メンバーでjson.dumps()を呼び出すことです。これは標準的な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にシリアライズする

195
steveha

試してみることができるというのは私にとって素晴らしい方法の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つは日付と時刻のシリアル化に関するもので、それから他のオブジェクトに対してはobj.__dict__が返されます。

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

json.dumps(myObj, default=serialize)

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

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

42
Broccoli

json.dumps()関数でdefaultという名前のパラメーターを指定できます。

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

説明:

ドキュメントを作成します( 2.7.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の答え ここ からこれを学びました。それが仕事をする最も簡単で最もきれいな方法であることがわかりました。

33
codeman48

私はただします:

data=json.dumps(myobject.__dict__)

これは完全な答えではありません、そしてあなたがある種の複雑なオブジェクトクラスを持っているなら、あなたは確かにすべてを得ることはないでしょう。しかし、私はこれを私の単純なオブジェクトのいくつかに使っています。

それが本当にうまくいくのは、OptionParserモジュールから得られる「options」クラスです。これは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)
22
SpiRail

jsonpickle を使う

import jsonpickle

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

これは洗練されていないクラスを直列化するための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}
4
GBGOLC

JSONは実際には任意のPythonオブジェクトをシリアル化するためのものではありません。 dictオブジェクトをシリアライズするのに最適ですが、pickleモジュールは本当に一般的に使用すべきものです。 pickleからの出力は、実際には人間が読める形式ではありませんが、問題が解決されるはずです。あなたがJSONを使うことを主張するなら、あなたはjsonpickleモジュールをチェックアウトすることができます、それは興味深いハイブリッドアプローチです。

https://github.com/jsonpickle/jsonpickle

3
Brendan Wood

Python3.x

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

しかしこれはCoDecです。

もう少し手を加えると、他の方法でクラスを構築できます。それをインスタンス化するためのデフォルトコンストラクタを想定し、それからクラス辞書を更新します。

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)

編集

もう少し研究を重ねると、メタクラスを使用して、スーパークラス registerメソッド呼び出しを必要とせずに一般化する方法が見つかりました。

import json
import collections

REGISTERED_CLASS = {}

class MetaSerializable(type):

    def __call__(cls, *args, **kwargs):
        if cls.__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

私は、受け入れられている答えで示唆されているような継承の代わりに、多態性を使うほうが良いと思います。そうでなければ、すべてのオブジェクトのエンコーディングをカスタマイズするために、大きな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)を呼び出します

1
hwat

これを実行する方法についていくつかの良い答えがあります。しかし、覚えておくべきことがいくつかあります。

  • インスタンスが大きなデータ構造の中にネストされているとどうなりますか?
  • クラス名も欲しい場合はどうしますか?
  • インスタンスを逆シリアル化したい場合はどうしますか?
  • __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__のソースを見るかもしれません。

それはまた、でこぼこした配列、日時、複素数のような他の型も行います。コメントも可能です。

0
Mark