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?
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
}
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]
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?
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
}
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]