Pythonクラスを直列化可能にする方法
簡単なクラス
class FileItem:
def __init__(self, fname):
self.fname = fname
私が出力を得ることができるようにするにはどうすればいいですか:
json.dumps()
エラーなし(FileItem instance at ... is not JSON serializable
)
予想される出力についての考えはありますか?例えばこれはしますか?
>>> f = FileItem("/foo/bar")
>>> magic(f)
'{"fname": "/foo/bar"}'
その場合、あなたは単にjson.dumps(f.__dict__)
を呼ぶことができます。
もっとカスタマイズされた出力が欲しいなら、 JSONEncoder
をサブクラス化してあなた独自のカスタムシリアル化を実装する必要があります。
簡単な例については、以下を参照してください。
>>> from json import JSONEncoder
>>> class MyEncoder(JSONEncoder):
def default(self, o):
return o.__dict__
>>> MyEncoder().encode(f)
'{"fname": "/foo/bar"}'
それから、このクラスをcls
kwargとして json.dumps()
メソッドに渡します。
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>
>>>
簡単な機能に対する簡単な解決策は次のとおりです。
.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"
}
より複雑なクラスの場合は、 jsonpickle というツールを検討できます。
jsonpickleは、JSONとの間での複雑なPythonオブジェクトのシリアライゼーションおよびデシリアライゼーションのためのPythonライブラリです。
Stdlibのjson、simplejson、demjsonなど、PythonをJSONにエンコードするための標準Pythonライブラリは、直接のJSONと同等のものを持つPythonプリミティブ(辞書、リスト、文字列、整数など)しか処理できません。 jsonpickleはこれらのライブラリの上に構築されており、より複雑なデータ構造をJSONにシリアル化することができます。 jsonpickleは高度に設定可能で拡張可能です - ユーザはJSONバックエンドを選択して追加のバックエンドを追加することができます。
答えのほとんどは、 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
これはあなたのクラスが単なる基本的なデータ表現である場合にはうまくいきます。
もう1つの選択肢は、JSONダンプを独自のクラスにラップすることです。
import json
class FileItem:
def __init__(self, fname):
self.fname = fname
def __repr__(self):
return json.dumps(self.__dict__)
あるいは、さらに良いことに、JsonSerializable
クラスからFileItemクラスをサブクラス化する:
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"}'
私は Onurの答え を好みますが、オブジェクトを自分自身で直列化するためのオプションのtoJSON()
メソッドを含めるように拡張します。
def dumper(obj):
try:
return obj.toJSON()
except:
return obj.__dict__
print json.dumps(some_big_object, default=dumper, indent=2)
私は先日この問題に遭遇し、入れ子になったオブジェクトを扱うと継承されたフィールドを持つことができるPythonオブジェクト用のより一般的なバージョンのEncoderを実装しました。
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"
}
次のようにクラスにto_json
メソッドを追加するだけです。
def to_json(self):
return self.message # or how you want it to be serialized
そして、このコード (from この答え ) を、一番上のどこかに追加してください。
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()はインポート時にjsonモジュールにモンキーパッチを適用するので、特別な "to_json()"メソッドを自動的にチェックし、見つかった場合はそれを使用してオブジェクトをエンコードします。
Onurが言ったように、今度はあなたのプロジェクトのすべてのjson.dumps()
を更新する必要はありません。
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', '[email protected]')))
標準のjson
を使用する場合、default
関数を定義する必要があります。
import json
def default(o):
return o._asdict()
print(json.dumps(User('alice', '[email protected]'), default=default))
Python 3.5以降を使用している場合は、 jsons
を使用できます。それはあなたのオブジェクト(そして再帰的に全ての属性)を辞書に変換します。
import jsons
a_dict = jsons.dump(your_object)
あるいは、文字列が欲しいなら:
a_str = jsons.dumps(your_object)
あるいはあなたのクラスがjsons.JsonSerializable
を実装しているなら:
a_dict = your_object.json
このクラスはトリックをすることができます、それはオブジェクトを標準の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.7
とpython3
で働いています。
json
は印刷できるオブジェクトの点で制限されており、jsonpickle
(pip install jsonpickle
が必要な場合があります)はテキストをインデントできないという点で制限されています。クラスを変更できないobjecthの内容を調べたいのであれば、私はまだ次のような単純な方法を見つけることができませんでした。
import json
import jsonpickle
...
print json.dumps(json.loads(jsonpickle.encode(object)), indent=2)
それでも、それらはオブジェクトメソッドを印刷できないことに注意してください。
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)
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__
ロードには2つのステップが必要です。今のところ、__python__
プロパティは使用されていません。
AlJohri の方法を使って、私はアプローチの人気をチェックする:
直列化(Python - > JSON):
to_json
:2018-06-27の266,595toJSON
:2018-06-27の96,307__json__
:2018-06-27の8,504for_json
:2018-06-27の6,937逆シリアル化(JSON - > Python):
from_json
:2018-06-27の226,101パッケージをインストールしても構わない場合は、 json-tricks を使用できます。
pip install json-tricks
その後は、jsonの代わりにjson_tricks
からdump(s)
をインポートするだけでよく、通常はうまくいきます。
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
がインポート可能で互換性のない方法で変更されていないことを前提としています。 あなたはインスタンスを取り戻すでしょう 、辞書や何かではなく、そしてそれはあなたが捨てたものと同一のコピーであるべきです。
あなたが何かが(非)シリアライズされる方法をカスタマイズしたいのなら、あなたはあなたのクラスに特別なメソッドを追加することができます。
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
例として、attributesパラメータの一部だけをシリアル化します。
そして無料のボーナスとして、あなたは(数えきれないほどの)配列、日付と時刻、順序付けされた地図、そしてjsonにコメントを含める能力のシリアル化を得ます。
免責事項:私はあなたと同じ問題を抱えていたので、私は json_tricks を作成しました。
jsonwebは私にとって最良の解決策のようです。 http://www.jsonweb.info/en/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"}'
これは私にはうまくいきました:
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))
PeeweeのモデルをPostgreSQLのJSONField
に保存しようとしたときに、この問題に遭遇しました。
しばらく苦労した後、これが一般的な解決策です。
私の解決策への鍵は、Pythonのソースコードを通して、コードドキュメンテーション( here で説明されています)が他のデータ型をサポートするために既存の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)
メソッドです。 Pythonから受け取るすべての... is not JSON serializable
苦情に対して、シリアライズ不可能な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に(やや)固有のものですが、私は思います:
json.dumps
がどのように機能するのかを理解していれば、このソリューションはPython(sans ORM)一般にも機能します。ご質問は、コメント欄に投稿してください。ありがとうございます。
これが私の3セントです...
これはツリーのようなpythonオブジェクトに対する明示的なjsonシリアライゼーションを示しています。
注:実際にこのようなコードが必要な場合は、 ひねった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)
あなたがパッケージをインストールすることができるならば、私は dill を試みることを勧めます。このパッケージのいいところは、pickle
と同じインターフェースを持っているということです。そのため、プロジェクトで既にpickle
を使用している場合は、コードを変更せずに単にdill
に置き換えてスクリプトを実行するかどうかを確認できます。だから試してみるのはとても安い解決策です!
(完全な非開示:私は決してディルプロジェクトと提携していないし、ディルプロジェクトに貢献したこともありません。)
パッケージをインストールしてください。
pip install dill
次に、コードを編集してdill
の代わりにpickle
をインポートします。
# import pickle
import dill as pickle
スクリプトを実行して、それが機能するかどうかを確認してください。 (もしそうなら、あなたはもはやpickle
モジュール名をシャドウイングしないようにあなたのコードをクリーンアップしたいかもしれません!)
プロジェクトページ から、dill
がシリアライズすることができる、またはできないデータ型に関するいくつかの詳細
dill
は以下の標準型を選択することができます。none、type、bool、int、long、float、複素数、str、Unicode、タプル、リスト、辞書、ファイル、バッファ、組み込み、新旧両方のスタイルクラス、セット、固定セット、配列、機能、例外
dill
はより多くの「エキゾチックな」標準型を漬けることもできます。yield付き関数、入れ子関数、ラムダ、セル、メソッド、非バインド方法、モジュール、コード、methodwrapper、dictproxy、methoddescriptor、getsetdescriptor、memberdescriptor、wrapperdescriptor、xrange、スライス、未実装、省略記号、終了
dill
はまだこれらの標準的な型をpickleすることができません:フレーム、ジェネレータ、トレースバック
私は自分の解決策を思いついた。このメソッドを使用し、シリアル化するために任意のドキュメント( dict 、 list 、 ObjectId etc)を渡します。
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
これは、すべての子を持つオブジェクトをJSONにシリアル化し、それを解析して戻す小さなライブラリです。
ここではシリアルバージョン管理やバックコンパチについて言及していないので、少しの間使用していたソリューションを投稿します。私はおそらくより多くのことを学ぶ必要があります。具体的にはJavaとJavascriptはおそらく私よりも成熟していますが、ここでは
https://Gist.github.com/andy-d/b7878d0044a4242c0498ed6d67fd50fe
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)
Datetimeオブジェクトのシリアル化問題を解決するためにデコレータを使うことにしました。これが私のコードです:
#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
上記のモジュールをインポートすることによって、私の他のモジュールは通常の方法で(defaultキーワードを指定せずに)jsonを使用して、日付時刻オブジェクトを含むデータをシリアル化します。日時シリアライザコードは、json.dumpsおよびjson.dumpに対して自動的に呼び出されます。