首頁  >  文章  >  後端開發  >  如何在不子類化`json.JSONEncoder`的情況下使自訂物件JSON可序列化?

如何在不子類化`json.JSONEncoder`的情況下使自訂物件JSON可序列化?

Barbara Streisand
Barbara Streisand原創
2024-10-30 20:25:30284瀏覽

How can I make custom objects JSON serializable without subclassing `json.JSONEncoder`?

使用常規編碼器使物件JSON 可序列化

將自訂不可序列化物件序列化為JSON 的預設方法是子類化json.JSONEncoder 並將自訂編碼器傳遞給json.dumps()。這通常如下所示:

<code class="python">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))</code>

但是,如果您想使用預設編碼器使物件可序列化怎麼辦?查看 json 模組的原始碼後,似乎直接擴展編碼器無法滿足此要求。

相反,您可以在套件的 __init__.py 初始化腳本中使用一種稱為「monkey-patching」的技術。這會影響所有後續的 JSON 模組序列化,因為模組通常只會載入一次,結果會快取在 sys.modules 中。

該補丁將修改預設 JSON 編碼器的預設方法以檢查唯一的「to_json」方法並利用它對找到的物件進行編碼。

為了簡單起見,這裡有一個作為獨立模組實現的示例:

<code class="python"># Module: make_json_serializable.py

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.</code>

使用此補丁很簡單:只需導入模組即可應用猴子-patch.

<code class="python"># Sample client script

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"}'</code>

要保留物件類型訊息,to_json 方法可以將其包含在傳回的字串中:

<code class="python">def to_json(self):
    """Convert to JSON format string representation."""
    return '{"type": "%s", "name": "%s"}' % (self.__class__.__name__, self.name)</code>

這會產生包含類別名稱的JSON:

{"type": "Foo", "name": "sazpaz"}

魔法就在這裡

更強大的方法是讓替換預設方法自動序列化大多數Python 對象,包括使用者定義的類別實例,而不需要唯一的方法。

在研究了幾種替代方案之後,以下基於pickle 的方法似乎最接近這個理想:

<code class="python"># Module: make_json_serializable2.py

from json import JSONEncoder
import pickle

def _default(self, obj):
    return {"_python_object": pickle.dumps(obj)}

JSONEncoder.default = _default  # Replace with the above.</code>

雖然並非所有內容都可以進行pickle(例如擴展類型),但pickle 提供了透過協定處理它們的方法使用獨特的方法。但是,這種方法涵蓋了更多情況。

反序列化

使用 pickle 協定透過在遇到「_python_object」時向 json.loads() 提供自訂 object_hook 函數參數來簡化重建原始 Python 物件字典中的 key。

<code class="python">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)</code>

這可以簡化為包裝函數:

<code class="python">json_pkloads = functools.partial(json.loads, object_hook=as_python_object)

pyobj = json_pkloads(json_str)</code>

此程式碼在 Python 3 中不起作用,因為 json.dumps() 傳回一個 bytes 物件JSONEncoder 無法處理。但是,此方法經過以下修改仍然有效:

<code class="python">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</code>

以上是如何在不子類化`json.JSONEncoder`的情況下使自訂物件JSON可序列化?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn