Une mise en œuvre plate
Vous pouvez utiliser quelque chose comme ceci:
from sqlalchemy.ext.declarative import DeclarativeMeta
class AlchemyEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj.__class__, DeclarativeMeta):
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)
fields[field] = data
except TypeError:
fields[field] = None
return fields
return json.JSONEncoder.default(self, obj)
puis convertissez en JSON en utilisant:
c = YourAlchemyClass()
print json.dumps(c, cls=AlchemyEncoder)
Il ignorera les champs qui ne sont pas encodables (définissez-les sur «Aucun»).
Il ne développe pas automatiquement les relations (car cela pourrait conduire à des auto-références et boucler pour toujours).
Une implémentation récursive et non circulaire
Si, cependant, vous préférez boucler indéfiniment, vous pouvez utiliser:
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):
if obj in _visited_objs:
return None
_visited_objs.append(obj)
fields = {}
for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata']:
fields[field] = obj.__getattribute__(field)
return fields
return json.JSONEncoder.default(self, obj)
return AlchemyEncoder
Et puis encodez les objets en utilisant:
print json.dumps(e, cls=new_alchemy_encoder(), check_circular=False)
Cela encoderait tous les enfants, et tous leurs enfants, et tous leurs enfants ... Potentiellement, encoder toute votre base de données, en gros. Quand il atteint quelque chose de son encodé avant, il l'encodera comme «Aucun».
Une implémentation sélective récursive, éventuellement circulaire
Une autre alternative, probablement meilleure, est de pouvoir spécifier les champs que vous souhaitez développer:
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):
if revisit_self:
if obj in _visited_objs:
return None
_visited_objs.append(obj)
fields = {}
for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata']:
val = obj.__getattribute__(field)
if isinstance(val.__class__, DeclarativeMeta) or (isinstance(val, list) and len(val) > 0 and isinstance(val[0].__class__, DeclarativeMeta)):
if field not in fields_to_expand:
fields[field] = None
continue
fields[field] = val
return fields
return json.JSONEncoder.default(self, obj)
return AlchemyEncoder
Vous pouvez maintenant l'appeler avec:
print json.dumps(e, cls=new_alchemy_encoder(False, ['parents']), check_circular=False)
Pour développer uniquement les champs SQLAlchemy appelés «parents», par exemple.