Una implementación plana
Podrías usar algo como esto:
from sqlalchemy.ext.declarative import DeclarativeMeta
class AlchemyEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj.__class__, DeclarativeMeta):
# an SQLAlchemy class
fields = {}
for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata']:
data = obj.__getattribute__(field)
try:
json.dumps(data) # this will fail on non-encodable values, like other classes
fields[field] = data
except TypeError:
fields[field] = None
# a json-encodable dict
return fields
return json.JSONEncoder.default(self, obj)
y luego convertir a JSON usando:
c = YourAlchemyClass()
print json.dumps(c, cls=AlchemyEncoder)
Ignorará los campos que no son codificables (configúrelos en 'Ninguno').
No expande automáticamente las relaciones (ya que esto podría conducir a auto-referencias y bucles para siempre).
Una implementación recursiva, no circular
Sin embargo, si prefieres un bucle para siempre, puedes usar:
from sqlalchemy.ext.declarative import DeclarativeMeta
def new_alchemy_encoder():
_visited_objs = []
class AlchemyEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj.__class__, DeclarativeMeta):
# don't re-visit self
if obj in _visited_objs:
return None
_visited_objs.append(obj)
# an SQLAlchemy class
fields = {}
for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata']:
fields[field] = obj.__getattribute__(field)
# a json-encodable dict
return fields
return json.JSONEncoder.default(self, obj)
return AlchemyEncoder
Y luego codificar objetos usando:
print json.dumps(e, cls=new_alchemy_encoder(), check_circular=False)
Esto codificaría a todos los hijos, y a todos sus hijos, y a todos sus hijos ... Básicamente, codifique potencialmente toda su base de datos. Cuando alcanza algo que está codificado anteriormente, lo codificará como 'Ninguno'.
Una implementación recursiva, posiblemente circular, selectiva
Otra alternativa, probablemente mejor, es poder especificar los campos que desea expandir:
def new_alchemy_encoder(revisit_self = False, fields_to_expand = []):
_visited_objs = []
class AlchemyEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj.__class__, DeclarativeMeta):
# don't re-visit self
if revisit_self:
if obj in _visited_objs:
return None
_visited_objs.append(obj)
# go through each field in this SQLalchemy class
fields = {}
for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata']:
val = obj.__getattribute__(field)
# is this field another SQLalchemy object, or a list of SQLalchemy objects?
if isinstance(val.__class__, DeclarativeMeta) or (isinstance(val, list) and len(val) > 0 and isinstance(val[0].__class__, DeclarativeMeta)):
# unless we're expanding this field, stop here
if field not in fields_to_expand:
# not expanding this field: set it to None and continue
fields[field] = None
continue
fields[field] = val
# a json-encodable dict
return fields
return json.JSONEncoder.default(self, obj)
return AlchemyEncoder
Ahora puede llamarlo con:
print json.dumps(e, cls=new_alchemy_encoder(False, ['parents']), check_circular=False)
Para expandir solo los campos de SQLAlchemy llamados 'padres', por ejemplo.