カスタムの非シリアル化可能オブジェクトをJSONシリアル化する通常の方法は、json.JSONEncoder
をサブクラス化し、カスタムエンコーダーをダンプに渡すことです。
通常は次のようになります。
class CustomEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, foo):
return obj.to_json()
return json.JSONEncoder.default(self, obj)
print json.dumps(obj, cls = CustomEncoder)
私がやろうとしているのは、デフォルトのエンコーダでシリアル化可能なものを作ることです。見回したが何も見つからなかった。私の考えでは、jsonエンコーディングを決定するためにエンコーダが調べるフィールドがいくつかあると思います。 __str__
に似たもの。おそらく__json__
フィールド。 Pythonにはこのようなものがありますか?
私が作っているモジュールの1つのクラスを、パッケージを使用するすべての人に対してJSONシリアル化できるようにしたいのです。
質問へのコメントで述べたように、json
モジュールのソースコードを見た後、あなたが望むことをするのに向いていないようです。ただし、目標は monkey-patching (質問 サルパッチとは何ですか? )これはパッケージの___init__.py
_初期化スクリプトで行うことができ、通常はモジュールが一度だけロードされ、結果が_sys.modules
_にキャッシュされるため、後続のすべてのjson
モジュールのシリアル化に影響します。
このパッチは、デフォルトのjsonエンコーダーのdefault
メソッド(デフォルトのdefault()
)を変更します。
簡単にするために、スタンドアロンモジュールとして実装した例を次に示します。
モジュール:_make_json_serializable.py
_
_""" Module that monkey-patches json module when it's imported so
JSONEncoder.default() automatically checks for a special "to_json()"
method and uses it to encode the object if found.
"""
from json import JSONEncoder
def _default(self, obj):
return getattr(obj.__class__, "to_json", _default.default)(obj)
_default.default = JSONEncoder.default # Save unmodified default.
JSONEncoder.default = _default # Replace it.
_
モジュールをインポートするだけでパッチが適用されるため、これを使用するのは簡単です。
サンプルクライアントスクリプト:
_import json
import make_json_serializable # apply monkey-patch
class Foo(object):
def __init__(self, name):
self.name = name
def to_json(self): # New special method.
""" Convert to JSON format string representation. """
return '{"name": "%s"}' % self.name
foo = Foo('sazpaz')
print(json.dumps(foo)) # -> "{\"name\": \"sazpaz\"}"
_
オブジェクトタイプ情報を保持するために、特別なメソッドはそれを返される文字列に含めることもできます:
_ return ('{"type": "%s", "name": "%s"}' %
(self.__class__.__name__, self.name))
_
これにより、クラス名を含む次のJSONが生成されます。
_"{\"type\": \"Foo\", \"name\": \"sazpaz\"}"
_
代替のdefault()
に特別な名前のメソッドを探すよりも、ほとんどのPythonオブジェクト)をシリアル化できるようになります自動的に、ユーザー定義のクラスインスタンスを含む、特別なメソッドを追加する必要なし。多くの代替案を調査した後、pickle
モジュールを使用する次のものがそれに最も近いように見えた私にとって理想的:
モジュール:_make_json_serializable2.py
_
_""" Module that imports the json module and monkey-patches it so
JSONEncoder.default() automatically pickles any Python objects
encountered that aren't standard JSON data types.
"""
from json import JSONEncoder
import pickle
def _default(self, obj):
return {'_python_object': pickle.dumps(obj)}
JSONEncoder.default = _default # Replace with the above.
_
もちろん、すべてをピクルスにすることはできません。たとえば、拡張タイプです。ただし、あなたが提案し、先に説明した方法と同様に、特別なメソッドを記述することにより、pickleプロトコルを介してそれらを処理する方法が定義されていますが、それははるかに少ないケースで必要になるでしょう。
とにかく、pickleプロトコルを使用すると、json.loads()
呼び出しでカスタム_object_hook
_関数引数を提供することにより、元のPythonオブジェクトを再構築するのがかなり簡単になります渡された辞書の_'_python_object'
_キーがあれば、それを使用していました。
_def as_python_object(dct):
try:
return pickle.loads(str(dct['_python_object']))
except KeyError:
return dct
pyobj = json.loads(json_str, object_hook=as_python_object)
_
これを多くの場所で行う必要がある場合は、追加のキーワード引数を自動的に提供するラッパー関数を定義する価値があります。
_json_pkloads = functools.partial(json.loads, object_hook=as_python_object)
pyobj = json_pkloads(json_str)
_
当然、これはjson
モジュールにもモンキーパッチされ、関数を(None
の代わりに)デフォルトの_object_hook
_にします。
answer by Raymond Hettinger から別のJSONシリアル化の質問にpickle
を使用するというアイデアを得ました。 Pythonコア開発者)のように。
json.dumps()
はbytes
が処理できないJSONEncoder
オブジェクトを返すため、上記のコードはPython 3に示すように機能しません。問題を回避する簡単な方法は、pickle.dumps()
から返された値を_latin1
_「デコード」し、_latin1
_から値を「エンコード」してから渡すことです。 pickle.loads()
関数のas_python_object()
へ。これは、任意のバイナリ文字列が有効な_latin1
_であり、常にUnicodeにデコードしてから元の文字列にエンコードし直すことができるためです( この答えSven Marnach )で指摘されているように。
(以下はPython 2では問題なく動作しますが、それが行う_latin1
_のデコードとエンコードは不要です。)
_from decimal import Decimal
class PythonObjectEncoder(json.JSONEncoder):
def default(self, obj):
return {'_python_object': pickle.dumps(obj).decode('latin1')}
def as_python_object(dct):
try:
return pickle.loads(dct['_python_object'].encode('latin1'))
except KeyError:
return dct
data = [1,2,3, set(['knights', 'who', 'say', 'ni']), {'key':'value'},
Decimal('3.14')]
j = json.dumps(data, cls=PythonObjectEncoder, indent=4)
data2 = json.loads(j, object_hook=as_python_object)
assert data == data2 # both should be same
_
Dictクラスは次のように拡張できます。
_#!/usr/local/bin/python3
import json
class Serializable(dict):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# hack to fix _json.so make_encoder serialize properly
self.__setitem__('dummy', 1)
def _myattrs(self):
return [
(x, self._repr(getattr(self, x)))
for x in self.__dir__()
if x not in Serializable().__dir__()
]
def _repr(self, value):
if isinstance(value, (str, int, float, list, Tuple, dict)):
return value
else:
return repr(value)
def __repr__(self):
return '<%s.%s object at %s>' % (
self.__class__.__module__,
self.__class__.__name__,
hex(id(self))
)
def keys(self):
return iter([x[0] for x in self._myattrs()])
def values(self):
return iter([x[1] for x in self._myattrs()])
def items(self):
return iter(self._myattrs())
_
クラスを通常のエンコーダーでシリアル化できるようにするには、「Serializable」を拡張します。
_class MySerializableClass(Serializable):
attr_1 = 'first attribute'
attr_2 = 23
def my_function(self):
print('do something here')
obj = MySerializableClass()
_
print(obj)
は次のようなものを出力します:
_<__main__.MySerializableClass object at 0x1073525e8>
_
print(json.dumps(obj, indent=4))
は次のようなものを出力します:
_{
"attr_1": "first attribute",
"attr_2": 23,
"my_function": "<bound method MySerializableClass.my_function of <__main__.MySerializableClass object at 0x1073525e8>>"
}
_
ハックをクラス定義に組み込むことをお勧めします。このように、クラスが定義されると、JSONがサポートされます。例:
import json
class MyClass( object ):
def _jsonSupport( *args ):
def default( self, xObject ):
return { 'type': 'MyClass', 'name': xObject.name() }
def objectHook( obj ):
if 'type' not in obj:
return obj
if obj[ 'type' ] != 'MyClass':
return obj
return MyClass( obj[ 'name' ] )
json.JSONEncoder.default = default
json._default_decoder = json.JSONDecoder( object_hook = objectHook )
_jsonSupport()
def __init__( self, name ):
self._name = name
def name( self ):
return self._name
def __repr__( self ):
return '<MyClass(name=%s)>' % self._name
myObject = MyClass( 'Magneto' )
jsonString = json.dumps( [ myObject, 'some', { 'other': 'objects' } ] )
print "json representation:", jsonString
decoded = json.loads( jsonString )
print "after decoding, our object is the first in the list", decoded[ 0 ]
JSONEncoder().default
のオーバーライドに関する問題は、一度しか実行できないことです。そのパターンで機能しない特別なデータ型につまずいた場合(奇妙なエンコーディングを使用している場合など)。以下のパターンを使用すると、シリアル化するクラスフィールド自体がシリアル化可能であれば(およびpythonリスト、ほとんど何も追加できません)、クラスJSONをいつでもシリアル化可能にすることができます。 、jsonフィールドに同じパターンを再帰的に適用する(またはシリアル化可能なデータを抽出する)必要があります。
# base class that will make all derivatives JSON serializable:
class JSONSerializable(list): # need to derive from a serializable class.
def __init__(self, value = None):
self = [ value ]
def setJSONSerializableValue(self, value):
self = [ value ]
def getJSONSerializableValue(self):
return self[1] if len(self) else None
# derive your classes from JSONSerializable:
class MyJSONSerializableObject(JSONSerializable):
def __init__(self): # or any other function
# ....
# suppose your__json__field is the class member to be serialized.
# it has to be serializable itself.
# Every time you want to set it, call this function:
self.setJSONSerializableValue(your__json__field)
# ...
# ... and when you need access to it, get this way:
do_something_with_your__json__field(self.getJSONSerializableValue())
# now you have a JSON default-serializable class:
a = MyJSONSerializableObject()
print json.dumps(a)
実稼働環境では、独自のカスタムエンコーダーでjson
の独自のモジュールを準備し、何かをオーバーライドすることを明確にします。モンキーパッチは推奨されませんが、testenvでモンキーパッチを実行できます。
例えば、
class JSONDatetimeAndPhonesEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, (datetime.date, datetime.datetime)):
return obj.date().isoformat()
Elif isinstance(obj, basestring):
try:
number = phonenumbers.parse(obj)
except phonenumbers.NumberParseException:
return json.JSONEncoder.default(self, obj)
else:
return phonenumbers.format_number(number, phonenumbers.PhoneNumberFormat.NATIONAL)
else:
return json.JSONEncoder.default(self, obj)
あなたが欲しい:
ペイロード= json.dumps(your_data、cls = JSONDatetimeAndPhonesEncoder)
または:
ペイロード= your_dumps(your_data)
または:
ペイロード= your_json.dumps(your_data)
ただし、テスト環境では、次のことを行ってください。
@pytest.fixture(scope='session', autouse=True)
def testenv_monkey_patching():
json._default_encoder = JSONDatetimeAndPhonesEncoder()
エンコーダをすべてに適用しますjson.dumps
回。
自分のクラスにserialize
関数を記述できない理由がわかりませんか?クラス自体にカスタムエンコーダーを実装し、「ピープル」がシリアル化関数を呼び出すことを許可します。この関数は、本質的に関数を取り除いたself.__dict__
を返します。
編集:
この質問 私に同意します。最も簡単な方法は、独自のメソッドを記述し、必要なJSONシリアル化データを返すことです。また、jsonpickleを試すことをお勧めしますが、正しいソリューションが組み込まれたときに、ビューティーの依存関係を追加します。