简体   繁体   English

如何从SQLAlchemy的JSON对象?

[英]How to jsonify objects from sqlalchemy?

I'm using Flask, SQLAlchemy and javascript. 我正在使用Flask,SQLAlchemy和javascript。 I need to pass the results of my query to javascript in the json format, through AJAX, but I keep getting this error: 我需要通过AJAX将查询结果以json格式传递给javascript,但是我一直收到此错误:

raise TypeError(repr(o) + " is not JSON serializable")
TypeError: <model.Cloud object at 0x7f72e40c5910> is not JSON serializable'

My code is as follows: 我的代码如下:

@app.route('/past/<user_id>')
def get_past_clouds(user_id):
    if user_id:
        clouds = model_session.query(model.User).filter_by(id=user_id).first().clouds
        if clouds != "":
            clouds_d = {}
            for idx, cloud in enumerate(clouds):
                clouds_d[idx] = cloud
            return jsonify(json_list=clouds_d)
    return None

Ignore my previous comment, that allowed the request to work but all the objects returned were undefined. 忽略我之前的评论,该评论允许请求工作,但返回的所有对象均未定义。

This is how I did it in the end: 这是我最后做的事情:

clouds_d = dict( (cloud.name, [ photo.to_dict() for photo in cloud.photos ]) for cloud in clouds )
return jsonify(clouds_d)

As you've discovered, SQLAlchemy models are not natively serializable to JSON. 正如您所发现的,SQLAlchemy模型不能本地化为JSON序列化。

The approach you adopted - to manually construct a dict from your model instance's properties - gets the job done, but if you need to jsonify multiple models, deal with value conversions on serialization, or support deserialization (converting JSON back to a SQLAlchemy model instance) you'll find yourself writing a lot of tedious code. 您采用的方法-从模型实例的属性手动构建字典-完成工作,但是如果您需要对多个模型进行JSON处理,在序列化中处理值转换或支持反序列化(将JSON转换回SQLAlchemy模型实例)您会发现自己编写了大量繁琐的代码。

A better solution is to use a serialization/deserialization extension library for SQLAlchemy, such as either SQLAthanor or Marshmallow-SQLAlchemy (full disclosure: I am the author of SQLAthanor). 更好的解决方案是对SQLAlchemy使用序列化/反序列化扩展库,例如SQLAthanorMarshmallow-SQLAlchemy (完整披露:我是SQLAthanor的作者)。

Using either of these libraries, you can basically serialize and deserialize SQLAlchemy models to other formats or deserialize them back into SQLAlchemy model instances. 使用这些库中的任何一个,您基本上都可以将SQLAlchemy模型序列化和反序列化为其他格式,或者将它们反序列化回SQLAlchemy模型实例。

In the case of SQLAthanor, you can basically get the job done with one method call: 对于SQLAthanor,基本上可以通过一个方法调用来完成工作:

json_result = model_instance.dump_to_json()

or (with some more configuration that gives you more fine-grained control): 或(具有更多配置,可让您进行更细粒度的控制):

json_result = model_instance.to_json()

You can also serialize to dict, or YAML, or CSV. 您还可以序列化为dict,YAML或CSV。

The reverse process is: 反向过程是:

model_instance = ModelClass.new_from_json(json_result)

Hope this helps! 希望这可以帮助!

Based on TypeError: <model.Cloud object at 0x7f72e40c5910> is not JSON serializable' it seems in your clouds_d dictionary has a Cloud object nested inside. 基于TypeError: <model.Cloud object at 0x7f72e40c5910> is not JSON serializable' ,似乎在clouds_d词典中嵌套了一个Cloud对象。 please like print clouds_d 请喜欢print clouds_d

Like the error message says, you cannot call jsonify on a SQLAlchemy model object (I don't think you can pickle them either). 就像错误消息所述,您不能在SQLAlchemy模型对象上调用jsonify (我也不认为您也可以使它们腌制)。 Try: 尝试:

clouds_d[idx] = dict((k, cloud.__dict__[k]) for k in cloud.__dict__ if k[0] != '_')

or better yet, be explicit: 或者更好的是,要明确:

clouds_d[idx] = {
    'id': cloud.id,
    'other_attrs': '...',
}

After some more fiddling around, I fixed it, here's how: 经过一些摆弄后,我将其修复,方法如下:

@app.route('/past/<user_id>')
def get_past_clouds(user_id):
    if user_id:
        clouds = model_session.query(model.User).filter_by(id=user_id).first().clouds
        if clouds != "":
            clouds_d = {}
            for idx, cloud in enumerate(clouds):
                clouds_d[idx] = [ photo.path + photo.filename for photo in cloud.photos ]
            print "clouds: ", clouds_d
            return json.dumps(clouds_d)
    return None

It looks like SQLAlchemy objects are not serializable, so I had to get out exactly what I wanted from the objects it was returning. 看起来SQLAlchemy对象是不可序列化的,所以我不得不从返回的对象中确切地弄出我想要的东西。

Here is my solution written in python: 这是我用python编写的解决方案:

step 1: custom JSON.encoder 步骤1:自定义JSON.encoder

import json
import uuid
from pprint import pformat
from datetime import datetime
from collections import OrderedDict

class CustomJSONEncoder(json.JSONEncoder):
    def default(self, obj):
        # cls = self.__class__
        if isinstance(obj, datetime):
            return obj.strftime('%Y-%m-%d %H:%M:%S.%f')
        elif isinstance(obj, uuid.UUID):
            return str(obj)
        elif hasattr(obj, '__html__'):
            return str(obj.__html__())
        elif isinstance(obj, OrderedDict):
            m = json.dumps(obj)
        elif hasattr(obj, 'to_dict'):
            return obj.to_dict()
        else:
            mp = pformat(obj, indent=2)
            print("JsonEncodeError", type(obj), mp)
            m = json.JSONEncoder.default(self, obj)
        return m

step 2: override flask_app.json_encoder 第2步:覆盖flask_app.json_encoder

from flask import Flask
app = Flask(__name__)
app.json_encoder = CustomJSONEncoder

step 3: Custom A Helper Class for metaclass of sqlalchemy 步骤3:为metaclass自定义一个Helper Class

class CoModel():
    """
    A `Helper Class` for `metaclass` of sqlalchemy
    usage 1 : flask_sqlalchemy

        from flask_sqlalchemy import SQLAlchemy
        db = SQLAlchemy(session_options=session_options)
        class TableName(db.Model, CoModel):
            pass


    usage 2: sqlalchemy

        from sqlalchemy.ext.declarative import declarative_base
        Base = declarative_base()
        class TableName(Base, CoModel):
            __tablename__ = 'table_name'
    """

    def to_dict(self):
        result = {}
        columns = self.columns()
        for col in columns:
            name = col.name
            value = getattr(self, name)
            result[name] = value
        return result

    @classmethod
    def columns(cls):
        tbl = getattr(cls, "__table__", None)
        if tbl is None:
            raise NameError('use sample: class TableName(db.Model, CoModel)')
        else:
            return tbl.columns

Finally, if you response with flask.jsonify , it will be auto encoded by CoModel.to_dict() 最后,如果您使用flask.jsonify响应,它将由CoModel.to_dict()自动编码

Of course, you can use json.dumps(sqlalchmey_object,cls=CustomJSONEncoder) as well. 当然,您也可以使用json.dumps(sqlalchmey_object,cls=CustomJSONEncoder)

Using marshmallow-sqlalchemy schemas, I'm generating an object and returning the serializable JSON object as a native method for the model. 使用棉花糖-sqlalchemy模式,我正在生成一个对象,并将可序列化的JSON对象作为模型的本机方法返回。 Here is an example for a class of Customer in SQLAlchemy model. 这是SQLAlchemy模型中的Customer类的示例。

from models import db
import datetime
from marshmallow_sqlalchemy import ModelSchema

class Customer(db.Model):
    __tablename__ = 'customers'
    __table_args__ = {'schema': 'abc_schema'}
    id = db.Column(db.String(255), primary_key=True)
    created_at = db.Column(db.DateTime, default=datetime.datetime.utcnow)
    firstName = db.Column(db.String())
    lastName = db.Column(db.String())
    def __getitem__(self, item):
        return getattr(self, item)

    ## Making the sql alchemy object JSON serailizable
    def JSONSerializableObj(self):
        class BaseSchema(ModelSchema): 
            def on_bind_field(self, field_name, field_obj):
                field_obj.allow_none = True

        class ObjectSchema(BaseSchema): 
            class Meta:
                model = self

        object_schema = ObjectSchema()
        return object_schema.dump(self).data

This way, I'm just reusing this snippet for all models and using the JSONSerializableObj 这样,我只是为所有模型重用此代码段,并使用JSONSerializableObj

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM