クラスインスタンスの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.'
何がおかしいのですか?
基本的な問題は、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}
上記のアプローチは、このブログ投稿で説明されています。
試してみることができるというのは私にとって素晴らしい方法の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__
を呼び出したくない場合は特に便利です。これは自動的に行われます。
あなたの考えを楽しみにして、これまでのところ私にとってとてもうまくいった。
json.dumps()
関数でdefault
という名前のパラメーターを指定できます。
json.dumps(obj, default=lambda x: x.__dict__)
説明:
``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の答え ここ からこれを学びました。それが仕事をする最も簡単で最もきれいな方法であることがわかりました。
私はただします:
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)
jsonpickle を使う
import jsonpickle
object = YourClass()
json_object = jsonpickle.encode(object)
これは洗練されていないクラスを直列化するための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}
JSONは実際には任意のPythonオブジェクトをシリアル化するためのものではありません。 dict
オブジェクトをシリアライズするのに最適ですが、pickle
モジュールは本当に一般的に使用すべきものです。 pickle
からの出力は、実際には人間が読める形式ではありませんが、問題が解決されるはずです。あなたがJSONを使うことを主張するなら、あなたはjsonpickle
モジュールをチェックアウトすることができます、それは興味深いハイブリッドアプローチです。
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)
を呼び出します
これを実行する方法についていくつかの良い答えがあります。しかし、覚えておくべきことがいくつかあります。
__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__
のソースを見るかもしれません。
それはまた、でこぼこした配列、日時、複素数のような他の型も行います。コメントも可能です。