Eine flache Implementierung
Sie könnten so etwas verwenden:
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)
und konvertieren Sie dann in JSON mit:
c = YourAlchemyClass()
print json.dumps(c, cls=AlchemyEncoder)
Felder, die nicht codierbar sind, werden ignoriert (setzen Sie sie auf 'Keine').
Beziehungen werden nicht automatisch erweitert (da dies zu Selbstreferenzen führen und für immer eine Schleife bilden kann).
Eine rekursive, nicht zirkuläre Implementierung
Wenn Sie jedoch lieber für immer eine Schleife erstellen möchten, können Sie Folgendes verwenden:
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
Und dann codieren Sie Objekte mit:
print json.dumps(e, cls=new_alchemy_encoder(), check_circular=False)
Dies würde alle Kinder und alle ihre Kinder und alle ihre Kinder codieren ... Im Grunde genommen möglicherweise Ihre gesamte Datenbank codieren. Wenn es etwas erreicht, das zuvor codiert wurde, wird es als "Keine" codiert.
Eine rekursive, möglicherweise kreisförmige, selektive Implementierung
Eine andere Alternative, wahrscheinlich besser, besteht darin, die Felder angeben zu können, die Sie erweitern möchten:
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
Sie können es jetzt aufrufen mit:
print json.dumps(e, cls=new_alchemy_encoder(False, ['parents']), check_circular=False)
Zum Beispiel nur um SQLAlchemy-Felder zu erweitern, die als "Eltern" bezeichnet werden.