How to return serialized JSON from Flask-SQLAlchemy relationship query?

Question:

i’m using Flask-SQLAlchemy and i have the following models with one to many relationship,

class User(db.Model):

    # Table name
    __tablename__ = "users"

    # Primary key
    user_id = db.Column(db.Integer, primary_key=True)

    # Fields (A-Z)
    email = db.Column(db.String(50), nullable=False, unique=True)
    password = db.Column(db.String, nullable=False)
    username = db.Column(db.String(50), unique=True)

    # Relationships (A-Z)
    uploads = db.relationship("Upload", backref="user")

class Upload(db.Model):

    # Table name
    __tablename__ = "uploads"

    # Primary key
    upload_id = db.Column(db.Integer, primary_key=True)

    # Fields (A-Z)
    name = db.Column(db.String(50), nullable=False)
    path_to_file = db.Column(db.String(256), nullable=False, unique=True)
    uploaded_by = db.Column(db.Integer, db.ForeignKey("users.user_id"))

and i want to return JSON like this:

{
    "users": [
        {
            "email": "[email protected]",
            "uploads": [
                {
                    "name": "1.png",
                    "path_to_file": "static/1.png"
                }
            ],
            "username": "maro"
        },
        {
            "email": "[email protected]",
            "uploads": [
                {
                    "name": "2.jpg",
                    "path_to_file": "static/2.jpg"
                }
            ],
            "username": "makos"
        }
    ]
}

So basically i want to return user object with all uploads (files user uploaded) in array.

I know i can access Upload class object within user with User.uploads (created with db.relationship) but i need some kind of serializer.

I wanted to add custom serialize() method to all my models:

# User serializer
    def serialize_user(self):
        if self.uploads:
            uploads = [upload.serialize_upload() for upload in self.uploads]
        return {
            "email": self.email,
            "password": self.password,
            "username": self.username,
            "uploads": uploads
        }

# Upload serializer
    def serialize_upload(self):
        if self.user:
            dict_user = self.user.serialize_user()
        return {
            "name": self.name,
            "path_to_file": self.path_to_file,
            "user": dict_user
        }

But problem with this is that i end up with nesting loop. My User object has upload files and each upload has it’s user’s data and these user’s data has uploads files…

My view endpoint:

@app.route('/users', methods=["GET"])
def get_users():
    users = [user.serialize_user() for user in User.query.all()]
    return jsonify(users)

Error:

RecursionError: maximum recursion depth exceeded while calling a Python object

Partial solution:

I can simply ommit serializing user object inside Upload serializer but then i won’t be able to create similiar endpoint but to get uploads.
Example: /uploads – JSON with all uploads and user object nested.

How can i effectively work with relationships to return them as serialized JSON data similiar to JSON structure above?

Asked By: user5482893

||

Answers:

As you said, you can simply write a second serializer method. So you keep the other one for your /uploads API call.

# User serializer
def serialize_user(self):
    if self.uploads:
        uploads = [upload.serialize_upload_bis() for upload in self.uploads]
    return {
            "email": self.email,
            "password": self.password,
            "username": self.username,
            "uploads": uploads
    }

# Upload serializer
def serialize_upload_bis(self):
    return {
        "name": self.name,
        "path_to_file": self.path_to_file,
    }

def serialize_upload(self):
    if self.user:
        dict_user = self.user.serialize_user()
    return {
        "name": self.name,
        "path_to_file": self.path_to_file,
        "user": dict_user
    }
Answered By: Hugo

Although the question is quite old, I want to add a more generic answer, because I also was faced with this issue. The following serializer works for all classes with relationships:

from sqlalchemy.orm.collections import InstrumentedList
class Serializer(object):

    def serialize(self):
        serializedObject= {}
        for c in inspect(self).attrs.keys():
            attribute = getattr(self, c)
            if(type(attribute) is InstrumentedList ):
                serializedObject[c]= Component.serialize_list(attribute)
            else:
                serializedObject[c]= attribute                
        return serializedObject

    @staticmethod
    def serialize_list(l):
        return [m.serialize() for m in l]
Answered By: Florian Schmid