Este grupo especializado luego de ciertos días de trabajo y de recopilar de datos, obtuvieron la respuesta, esperamos que resulte de gran utilidad para tu trabajo.
Solución:
Llaves con protagonismo _
Los subrayados no están realmente ‘ocultos’, son solo más cadenas de JSON. El constructo Container
clase es solo un diccionario con orden, el _io
key no es nada especial para esa clase.
Tienes dos opciones:
- implementar un
default
gancho que solo devuelve un valor de reemplazo. - Filtrar el keypares de valores que sabes que no pueden funcionar antes de serialización.
y quizás un tercero, pero un escaneo casual de las páginas del proyecto de Construct no me dice si está disponible: tiene JSON de salida de Construct o al menos un diccionario compatible con JSON, tal vez usando adaptadores.
El gancho predeterminado no puede evitar _io
key se agregue a la salida, pero le permitiría al menos evitar el error:
json.dumps(packet, default=lambda o: '')
El filtrado se puede realizar de forma recursiva; el @functools.singledispatch()
El decorador puede ayudar a mantener limpio dicho código:
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
Tengo la implementación anterior un adicional skip_underscore
argumento también, para omitir explícitamente keys que tienen un _
personaje al principio. Esto ayudaría a omitir todos los “ocultos” adicionales attributes la biblioteca Construct está usando.
Ya que Container
es un dict
subclase, el código anterior manejará automáticamente instancias como packet
.
Ignorar un campo no serializable requiere una lógica adicional pesada, como se señaló correctamente en todas las respuestas anteriores.
Si realmente no necesita excluir el campo, puede generar un valor predeterminado en su lugar:
def safe_serialize(obj):
default = lambda o: f"<>"
return json.dumps(obj, default=default)
obj = "a": 1, "b": bytes() # bytes is non-serializable by default
print(safe_serialize(obj))
Eso producirá este resultado:
"a": 1, "b": "<>"
Este código imprimirá el nombre del tipo, lo que podría ser útil si desea implementar sus serializadores personalizados más adelante.
skipkeys
no hace lo que piensas que hace, instruye al json.JSONEncoder
saltear keys que no son de un básico tipo, no los valores del keys – es decir, si tuvieras un dict
object(): "foobar"
se saltaría el object()
key, mientras que sin skipkeys
ajustado a True
levantaría un TypeError
.
Puedes sobrecargar JSONEncoder.iterencode()
(y su parte más vulnerable) y realice un filtrado de anticipación allí, pero terminará reescribiendo prácticamente el json
módulo, ralentizándolo en el proceso ya que no podrá beneficiarse de las partes compiladas. Lo que le sugiero es que procese previamente sus datos a través del filtrado iterativo y omita keys/ tipos que no desea en su JSON final. Entonces la json
El módulo debería poder procesarlo sin instrucciones adicionales. Algo como:
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
Luego crea tu filtro:
import io
preprocessor = SkipFilter([io.BytesIO], ["_io"]) # double-whammy skip of io.BytesIO
En este caso, debería bastar con saltar solo por tipo, pero en caso de que el _io
key contiene algunos otros datos indeseables, lo que garantiza que no estarán en el resultado final. De todos modos, puede filtrar los datos antes de pasarlos al JSONEncoder
:
import json
json_data = json.dumps(preprocessor.filter(packet)) # no _io keys or io.BytesIO data...
Por supuesto, si su estructura contiene algunos otros datos exóticos o datos que se representan en JSON de manera diferente según su tipo, este enfoque podría estropearlo ya que convierte todas las asignaciones en dict
y todas las secuencias en list
. Sin embargo, para uso general esto debería ser más que suficiente.
Te invitamos a estimular nuestra labor mostrando un comentario o valorándolo te estamos eternamente agradecidos.