Skip to content

Wie man json.dumps in Python dazu bringt, ein nicht serialisierbares Feld zu ignorieren

Lösung:

Tasten mit führendem _ Unterstriche sind nicht wirklich "versteckt", sie sind nur mehr Zeichenfolgen für JSON. Das Konstrukt Container Klasse ist nur ein Wörterbuch mit Ordnung, die _io key ist für diese Klasse nichts Besonderes.

Sie haben zwei Möglichkeiten:

  • implementieren default Hook, der nur einen Ersatzwert zurückgibt.
  • Filtern Sie die Schlüssel-Wert-Paare heraus, von denen Sie wissen, dass sie nicht funktionieren können Vor Serialisieren.

und vielleicht ein drittes, aber ein gelegentlicher Scan der Construct-Projektseiten sagt mir nicht, ob es verfügbar ist: Construct-Ausgabe-JSON oder zumindest ein JSON-kompatibles Wörterbuch, möglicherweise mithilfe von Adaptern.

Der Standard-Hook kann das nicht verhindern _io key nicht zur Ausgabe hinzugefügt werden, aber Sie würden den Fehler zumindest vermeiden:

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

Die Filterung kann rekursiv erfolgen; das @functools.singledispatch() Decorator kann helfen, solchen Code sauber zu halten:

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

Ich habe die obige Implementierung zusätzlich skip_underscore Argument auch, um Schlüssel mit a . explizit zu überspringen _ Charakter am Anfang. Dies würde helfen, alle zusätzlichen 'versteckten' Attribute zu überspringen, die die Construct-Bibliothek verwendet.

Schon seit Container ist ein dict Unterklasse behandelt der obige Code automatisch Instanzen wie packet.

Das Ignorieren eines nicht serialisierbaren Felds erfordert eine starke zusätzliche Logik, wie in allen vorherigen Antworten richtig hervorgehoben wurde.

Wenn Sie das Feld nicht wirklich ausschließen müssen, können Sie stattdessen einen Standardwert generieren:

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))

Das führt zu diesem Ergebnis:

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

Dieser Code gibt den Typnamen aus, was nützlich sein kann, wenn Sie Ihre benutzerdefinierten Serialisierer später implementieren möchten.

skipkeys tut nicht das, was Sie vielleicht denken, es tut - es befiehlt dem json.JSONEncoder Tasten überspringen, die nicht von a . sind Basic geben Sie ein, nicht die Werte der Schlüssel - dh wenn Sie a dict {object(): "foobar"} es würde überspringen object() Schlüssel, während ohne skipkeys einstellen True es würde a anheben TypeError.

Du kannst überladen JSONEncoder.iterencode() (und seinen Unterbauch) und führen Sie dort eine Look-Ahead-Filterung durch, aber Sie werden das ziemlich neu schreiben json Modul und verlangsamen es dabei, da Sie nicht von den kompilierten Teilen profitieren können. Ich würde Ihnen vorschlagen, Ihre Daten über iteratives Filtern vorzuverarbeiten und Schlüssel/Typen zu überspringen, die Sie nicht in Ihrem endgültigen JSON haben möchten. Dann ist die json Modul sollte es ohne zusätzliche Anweisungen verarbeiten können. Etwas wie:

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

Dann erstellen Sie Ihren Filter:

import io

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

In diesem Fall sollte es ausreichen, nur nach Typ zu überspringen, aber für den Fall, dass die _io key enthält einige andere unerwünschte Daten, die garantiert, dass sie nicht im Endergebnis enthalten sind. Wie auch immer, Sie können die Daten dann einfach filtern, bevor Sie sie an die weitergeben JSONEncoder:

import json

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

Wenn Ihre Struktur andere exotische Daten enthält oder Daten, die in JSON je nach Typ unterschiedlich dargestellt werden, kann dieser Ansatz natürlich durcheinander kommen, da alle Mappings in . umgewandelt werden dict und alle Sequenzen in list. Für den allgemeinen Gebrauch sollte dies jedoch mehr als ausreichend sein.

Click to rate this post!
[Total: 0 Average: 0]



Anderer Beitrag

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.