63

シリアル化できないカスタム オブジェクトを JSON シリアル化する通常の方法は、サブクラスjson.JSONEncoder化してから、カスタム エンコーダーを に渡すことjson.dumps()です。

通常は次のようになります。

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 シリアライズ可能にしたいと考えています。

4

6 に答える 6

90

あなたの質問へのコメントで述べたように、jsonモジュールのソースコードを見た後、あなたが望むことをするのに向いていないようです。しかし、この目標は、モンキー パッチと呼ばれるものによって達成できます(モンキーパッチとは? の 質問を参照してください)。これはパッケージの初期化スクリプトで行うことができ、モジュールは通常 1 回だけロードされ、結果は にキャッシュされるため、__init__.py後続のすべてのjsonモジュールのシリアル化に影響しsys.modulesます。

このパッチは、デフォルトの 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\"}"

魔法はここにあります

特別な名前のメソッドを置き換えるよりもさらに良いのは、特別なメソッドを追加する必要なく、ユーザー定義のクラス インスタンスを含むほとんどの Python オブジェクトを自動的default()にシリアライズできることです。いくつかの代替案を調査した後、モジュールを使用する @Raymond Hettinger による別の質問への回答に基づいて、次のものがその理想に最も近いように思えました。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 プロトコルを使用すると、渡されたディクショナリ内の任意のキーを使用する任意の呼び出しにカスタムobject_hook関数引数を提供することで、元の Python オブジェクトを再構築することがかなり簡単になることも意味します。何かのようなもの:json.loads()'_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モジュールにモンキー パッチを適用して、関数をobject_hook(の代わりにNone) デフォルトにすることもできます。

Raymond Hettingerによる別の JSON シリアライゼーションの質問への回答pickleから使用するアイデアを得ました。これは、非常に信頼できるものであり、公式のソース (Python コア開発者のように) と考えています。

Python 3 への移植性

が処理できないオブジェクトをjson.dumps()返すため、上記のコードは Python 3 で示されているようには機能しません。ただし、アプローチはまだ有効です。この問題を回避する簡単な方法は、返された値を「デコード」してから「エンコード」してから関数に渡すことです。これは、任意のバイナリ文字列が有効であり、常に Unicode にデコードしてから元の文字列に再度エンコードできるためです (この回答Sven Marnachが指摘したように)。bytesJSONEncoderlatin1pickle.dumps()latin1pickle.loads()as_python_object()latin1

(以下は 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


class Foo(object):  # Some user-defined class.
    def __init__(self, name):
        self.name = name

    def __eq__(self, other):
        if type(other) is type(self):  # Instances of same class?
            return self.name == other.name
        return NotImplemented

    __hash__ = None


data = [1,2,3, set(['knights', 'who', 'say', 'ni']), {'key':'value'},
        Foo('Bar'), Decimal('3.141592653589793238462643383279502884197169')]
j = json.dumps(data, cls=PythonObjectEncoder, indent=4)
data2 = json.loads(j, object_hook=as_python_object)
assert data == data2  # both should be same
于 2013-09-01T17:35:02.487 に答える
0

serialize独自のクラスの関数を作成できない理由がわかりませんか? クラス自体の内部にカスタム エンコーダーを実装し、"人々" がシリアル化関数を呼び出せるようにself.__dict__します。

編集:

この質問は、最も簡単な方法は、独自のメソッドを作成し、必要な json シリアル化されたデータを返すことであることに同意します。また、jsonpickle を試すこともお勧めしますが、正しいソリューションが組み込まれたときに、美しさのための追加の依存関係を追加することになります。

于 2013-08-31T21:36:49.703 に答える