web-dev-qa-db-ja.com

json.dumpsをPythonシリアライズ不可能なフィールドを無視する方法

Construct2.9ライブラリでいくつかのバイナリデータを解析する出力をシリアル化しようとしています。結果をJSONにシリアル化したい。

packetは、ConstructクラスContainerのインスタンスです。

BytesIO型の隠された__io_が含まれているようです-以下のdict(packet)の出力を参照してください。

_{
'packet_length': 76, 'uart_sent_time': 1, 'frame_number': 42958, 
'subframe_number': 0, 'checksum': 33157, '_io': <_io.BytesIO object at 0x7f81c3153728>, 
'platform':661058, 'sync': 506660481457717506, 'frame_margin': 20642,
'num_tlvs': 1, 'track_process_time': 593, 'chirp_margin': 78,
'timestamp': 2586231182, 'version': 16908293
}
_

json.dumps(packet)を呼び出すと、明らかにTypeErrorが発生します。

_...

File "/usr/lib/python3.5/json/__init__.py", line 237, in dumps
    **kw).encode(obj)
File "/usr/lib/python3.5/json/encoder.py", line 198, in encode
    chunks = self.iterencode(o, _one_shot=True)
File "/usr/lib/python3.5/json/encoder.py", line 256, in iterencode
    return _iterencode(o, 0)
File "/usr/lib/python3.5/json/encoder.py", line 179, in default
    raise TypeError(repr(o) + " is not JSON serializable")
TypeError: <_io.BytesIO object at 0x7f81c3153728> is not JSON serializable
_

しかし、私が混乱しているのは、json.dumps(packet, skipkeys=True)を実行するとまったく同じエラーが発生し、__io_フィールドをスキップすることを期待していることです。ここで問題は何ですか? skipkeysで__io_フィールドをスキップできないのはなぜですか?

JSONEncoderをオーバーライドし、None型のフィールドに対してBytesIOを返すことでコードが機能するようになりましたが、それはつまり、シリアル化された文字列に_"_io": null_要素の負荷が含まれることを意味しますまったく持ちたくない...

7
mz8i

先頭に___アンダースコアが付いたキーは実際には「非表示」ではなく、JSONへの単なる文字列です。 Construct Containerクラスは単なる順序付きの辞書であり、__io_キーはそのクラスにとって特別なものではありません。

次の2つのオプションがあります。

  • 置換値を返すだけのdefaultフックを実装します。
  • シリアル化する前に動作しないことがわかっているキーと値のペアを除外します

おそらく3番目ですが、Constructプロジェクトページのカジュアルスキャンでは、利用可能かどうかはわかりません。出力JSONまたは少なくともJSON互換の辞書を、おそらくアダプターを使用して構築します。

デフォルトのフックでは、__io_キーが出力に追加されるのを防ぐことはできませんが、少なくともエラーを回避できます:

_json.dumps(packet, default=lambda o: '<not serializable>')
_

フィルタリングは再帰的に実行できます。 @functools.singledispatch() decorator は、このようなコードをクリーンに保つのに役立ちます。

_from functools import singledispatch

_cant_serialize = object()

@singledispatch
def json_serializable(object, skip_underscore=False):
    """Filter a Python object to only include serializable object types

    In dictionaries, keys are converted to strings; if skip_underscore is true
    then keys starting with an underscore ("_") are skipped.

    """
    # default handler, called for anything without a specific
    # type registration.
    return _cant_serialize

@json_serializable.register(dict)
def _handle_dict(d, skip_underscore=False):
    converted = ((str(k), json_serializable(v, skip_underscore))
                 for k, v in d.items())
    if skip_underscore:
        converted = ((k, v) for k, v in converted if k[:1] != '_')
    return {k: v for k, v in converted if v is not _cant_serialize}

@json_serializable.register(list)
@json_serializable.register(Tuple)
def _handle_sequence(seq, skip_underscore=False):
    converted = (json_serializable(v, skip_underscore) for v in seq)
    return [v for v in converted if v is not _cant_serialize]

@json_serializable.register(int)
@json_serializable.register(float)
@json_serializable.register(str)
@json_serializable.register(bool)  # redudant, supported as int subclass
@json_serializable.register(type(None))
def _handle_default_scalar_types(value, skip_underscore=False):
    return value
_

上記の実装には、追加の_skip_underscore_引数もあり、開始時に___文字を持つキーを明示的にスキップします。これは、Constructライブラリが使用している追加の「隠された」属性をすべてスキップするのに役立ちます。

Containerdictサブクラスであるため、上記のコードはpacketなどのインスタンスを自動的に処理します。

12
Martijn Pieters

skipkeys はあなたが思っていることをしません- json.JSONEncoder にないキーをスキップするように指示しますbasicキーの値ではなくタイプ。つまり、dict{object(): "foobar"}があった場合、object()キーはスキップされますが、skipkeysTrueは、TypeErrorを発生させます。

JSONEncoder.iterencode() (およびその下腹部)をオーバーロードし、そこで先読みフィルタリングを実行できますが、最終的にはjsonモジュールを書き換えて、コンパイルされたパーツの恩恵を受けることができないため、プロセス。繰り返しフィルタリングを使用してデータを前処理し、最終的なJSONで不要なキー/タイプをスキップすることをお勧めします。その後、jsonモジュールは、追加の指示なしでそれを処理できるはずです。何かのようなもの:

import collections

class SkipFilter(object):

    def __init__(self, types=None, keys=None, allow_empty=False):
        self.types = Tuple(types or [])
        self.keys = set(keys or [])
        self.allow_empty = allow_empty  # if True include empty filtered structures

    def filter(self, data):
        if isinstance(data, collections.Mapping):
            result = {}  # dict-like, use dict as a base
            for k, v in data.items():
                if k in self.keys or isinstance(v, self.types):  # skip key/type
                    continue
                try:
                    result[k] = self.filter(v)
                except ValueError:
                    pass
            if result or self.allow_empty:
                return result
        Elif isinstance(data, collections.Sequence):
            result = []  # a sequence, use list as a base
            for v in data:
                if isinstance(v, self.types):  # skip type
                    continue
                try:
                    result.append(self.filter(v))
                except ValueError:
                    pass
            if result or self.allow_empty:
                return result
        else:  # we don't know how to traverse this structure...
            return data  # return it as-is, hope for the best...
        raise ValueError

次に、フィルターを作成します。

import io

preprocessor = SkipFilter([io.BytesIO], ["_io"])  # double-whammy skip of io.BytesIO

この場合、タイプごとにスキップするだけで十分ですが、_ioキーが他の望ましくないデータを保持している場合は、最終結果に含まれないことが保証されます。とにかく、JSONEncoderに渡す前にデータをフィルタリングするだけです。

import json

json_data = json.dumps(preprocessor.filter(packet))  # no _io keys or io.BytesIO data...

もちろん、構造に他のエキゾチックなデータまたはJSONでそのタイプに基づいて異なる形で表されるデータが含まれる場合、このアプローチは、すべてのマッピングをdictに、すべてのシーケンスをlistに変えるため、混乱する可能性があります。ただし、一般的な使用法ではこれで十分です。

2
zwer

シリアル化できないフィールドを無視するには、以前のすべての回答で正しく指摘されているように、重い追加のロジックが必要です。

フィールドを本当に除外する必要がない場合は、代わりにデフォルト値を生成できます。

def safe_serialize(obj):
  default = lambda o: f"<<non-serializable: {type(o).__qualname__}>>"
  return json.dumps(obj, default=default)

obj = {"a": 1, "b": bytes()} # bytes is non-serializable by default
print(safe_serialize(obj))

これにより、次の結果が生成されます。

{"a": 1, "b": "<<non-serializable: bytes>>"}

このコードは型名を出力します。これは、後でカスタムシリアライザーを実装する場合に役立ちます。

1