pythonオブジェクトをJSONで(simplejsonを使用して)シリアル化しようとしていますが、オブジェクトが「JSONシリアル化可能ではない」というエラーが発生します。
クラスは、整数、文字列、浮動小数点のみのフィールドを持つ単純なクラスであり、1つの親スーパークラスから同様のフィールドを継承します。例:
class ParentClass:
def __init__(self, foo):
self.foo = foo
class ChildClass(ParentClass):
def __init__(self, foo, bar):
ParentClass.__init__(self, foo)
self.bar = bar
bar1 = ChildClass(my_foo, my_bar)
bar2 = ChildClass(my_foo, my_bar)
my_list_of_objects = [bar1, bar2]
simplejson.dump(my_list_of_objects, my_filename)
ここで、foo、barは、上記のような単純なタイプです。唯一のトリッキーなことは、ChildClassが(ParentClassまたはChildClassではない型の)別のオブジェクトを参照するフィールドを持っていることがあります。
Simplejsonでこれをjsonオブジェクトとしてシリアル化する最も簡単な方法は何ですか?辞書としてシリアライズ可能にするだけで十分ですか? ChildClassのdictメソッドを単純に記述する最善の方法はありますか?最後に、別のオブジェクトを参照するフィールドがあると、事態が大幅に複雑になりますか?もしそうなら、コードを書き直して、クラスに単純なフィールド(文字列/浮動小数点など)のみを含めることができます。
ありがとうございました。
私は過去にこの戦略を使用したことがあり、かなり満足しています。次の構造を持つカスタムオブジェクトをJSONオブジェクトリテラル(Python dict
sなど)としてエンコードします。
_{ '__ClassName__': { ... } }
_
これは本質的に1アイテムのdict
であり、その単一のキーはエンコードされるオブジェクトの種類を指定する特別な文字列であり、その値はインスタンスの属性のdict
です。それが理にかなっている場合。
エンコーダーとデコーダーの非常に単純な実装(実際に使用したコードから簡略化したもの)は次のようになります。
_TYPES = { 'ParentClass': ParentClass,
'ChildClass': ChildClass }
class CustomTypeEncoder(json.JSONEncoder):
"""A custom JSONEncoder class that knows how to encode core custom
objects.
Custom objects are encoded as JSON object literals (ie, dicts) with
one key, '__TypeName__' where 'TypeName' is the actual name of the
type to which the object belongs. That single key maps to another
object literal which is just the __dict__ of the object encoded."""
def default(self, obj):
if isinstance(obj, TYPES.values()):
key = '__%s__' % obj.__class__.__name__
return { key: obj.__dict__ }
return json.JSONEncoder.default(self, obj)
def CustomTypeDecoder(dct):
if len(dct) == 1:
type_name, value = dct.items()[0]
type_name = type_name.strip('_')
if type_name in TYPES:
return TYPES[type_name].from_dict(value)
return dct
_
この実装では、エンコードしているオブジェクトに、JSONからデコードされたdict
からインスタンスを再作成する方法を知っているfrom_dict()
クラスメソッドがあると想定しています。
エンコーダーとデコーダーを拡張してカスタムタイプ(datetime
オブジェクトなど)をサポートするのは簡単です。
[〜#〜] edit [〜#〜]、あなたの編集に答える:このような実装の良い点は、TYPES
マッピング。つまり、次のように自動的にChildClassを処理します。
_class ChildClass(object):
def __init__(self):
self.foo = 'foo'
self.bar = 1.1
self.parent = ParentClass(1)
_
その結果、JSONは次のようになります。
_{ '__ChildClass__': {
'bar': 1.1,
'foo': 'foo',
'parent': {
'__ParentClass__': {
'foo': 1}
}
}
}
_
customクラスのインスタンスは、次の関数を使用してJSON形式の文字列として表すことができます。
def json_repr(obj):
"""Represent instance of a class as JSON.
Arguments:
obj -- any object
Return:
String that reprent JSON-encoded object.
"""
def serialize(obj):
"""Recursively walk object's hierarchy."""
if isinstance(obj, (bool, int, long, float, basestring)):
return obj
Elif isinstance(obj, dict):
obj = obj.copy()
for key in obj:
obj[key] = serialize(obj[key])
return obj
Elif isinstance(obj, list):
return [serialize(item) for item in obj]
Elif isinstance(obj, Tuple):
return Tuple(serialize([item for item in obj]))
Elif hasattr(obj, '__dict__'):
return serialize(obj.__dict__)
else:
return repr(obj) # Don't know how to handle, convert to string
return json.dumps(serialize(obj))
この関数は、JSON形式の文字列を生成します
カスタムクラスのインスタンス、
葉としてカスタムクラスのインスタンスを持つ辞書、
PythonのJSONドキュメントで指定されている// help(json.dumps)
//>
カスタムタイプ変換を提供するために、JSONEncoder
のdefault()
メソッドをオーバーライドし、それをcls
引数として渡す必要があります。
これは、Mongoの特別なデータ型(datetimeおよびObjectId)をカバーするために使用するものです
class MongoEncoder(json.JSONEncoder):
def default(self, v):
types = {
'ObjectId': lambda v: str(v),
'datetime': lambda v: v.isoformat()
}
vtype = type(v).__name__
if vtype in types:
return types[type(v).__name__](v)
else:
return json.JSONEncoder.default(self, v)
簡単に言うと
data = json.dumps(data, cls=MongoEncoder)
Djangoを使用している場合は、Djangoのシリアライザモジュールを介して簡単に実行できます。詳細はここにあります: https://docs.djangoproject.com/en/dev/topics/serialization/
同様の問題がありますが、json.dump
関数が呼び出されません。したがって、MyClass
JSONをシリアライズ可能にするjson.dump
にカスタムエンコーダーを指定せずには、jsonエンコーダーにMonkeyパッチを適用する必要があります。
まず、モジュールmy_module
にエンコーダを作成します。
import json
class JSONEncoder(json.JSONEncoder):
"""To make MyClass JSON serializable you have to Monkey patch the json
encoder with the following code:
>>> import json
>>> import my_module
>>> json.JSONEncoder.default = my_module.JSONEncoder.default
"""
def default(self, o):
"""For JSON serialization."""
if isinstance(o, MyClass):
return o.__repr__()
else:
return super(self,o)
class MyClass:
def __repr__(self):
return "my class representation"
次に、コメントで説明されているように、サルがjsonエンコーダーにパッチを適用します。
import json
import my_module
json.JSONEncoder.default = my_module.JSONEncoder.default
これで、外部ライブラリ(cls
パラメータを変更できない場所)でのjson.dump
の呼び出しでも、my_module.MyClass
オブジェクトに対して機能します。
私が考えている2つの解決策を今すぐ読み直すのは少しばかげています。もちろん、Django-rest-frameworkを使用すると、このフレームワークには、上記のこの問題に対応する優れた機能が組み込まれています。
ウェブサイトで このモデルビューの例 を参照してください
Django-rest-frameworkを使用していない場合、これはとにかく役立ちます:
このページで、この問題に役立つ2つの解決策を見つけました:(2番目が一番好きです!)
可能な解決策1(またはその方法): David Chambers Designは素晴らしい解決策を作りました
Davidが彼のソリューションコードをここにコピーアンドペーストしても構わないと思います。
インスタンスのモデルでシリアル化メソッドを定義します。
def toJSON(self):
import simplejson
return simplejson.dumps(dict([(attr, getattr(self, attr)) for attr in [f.name for f in self._meta.fields]]))
そして彼は上記の方法を抽出したので、より読みやすくなっています:
def toJSON(self):
fields = []
for field in self._meta.fields:
fields.append(field.name)
d = {}
for attr in fields:
d[attr] = getattr(self, attr)
import simplejson
return simplejson.dumps(d)
心に留めてください、それは私の解決策ではありません、すべてのクレジットは含まれているリンクに行きます。これはスタックオーバーフローになるはずだと思っただけです。
これは上記の回答でも実装できます。
解決策2:
私の好ましい解決策はこのページにあります:
http://www.traddicts.org/webdevelopment/flexible-and-simple-json-serialization-for-Django/
ちなみに、私はこの2番目の最良のソリューションの作者を見ました:stackoverflowにもあります:
私は彼がこれを見てほしいと思います、そして私たちはオープンソリューションで彼のコードを実装し、改善し始めることについて話すことができますか?
これは一種のハックであり、おそらくそれが間違っている可能性のある多くのものがあると確信しています。しかし、私は単純なスクリプトを作成していて、モデルオブジェクトのリストをシリアル化するためにjsonシリアライザーをサブクラス化したくないという問題を実行しました。結局、リスト内包表記を使ってしまいました
Let:アセット=モデルオブジェクトのリスト
コード:
myJson = json.dumps([x.__dict__ for x in assets])
これまでのところ、私のニーズには魅力的に機能しているようです