blog
blog copied to clipboard
Serializing Resources to and from JSON
A frequent need when writing a web service is to convert internal representation of resources to and from JSON, which is the transport format used in HTTP requests and responses.
to_json
Add a new to_json() method to the class.
class Post(db.Model):
# ...
def to_json():
return {
'url': url_for('api.get_post', id=self.id, _external=True),
'body': self.body,
'body_html': self.body_html,
'timestamp': self.timestamp,
'author': url_for('api.get_user', id=self.author_id, _external=True),
'comments': url_for('api.get_post_comments', id=self.id, _external=True),
'comment_count': self.comments.count()
}
This url, author and comments fields need to return the URLs for the respective resource, so these are generated with url_for() calls to routes that will be defined in the API blueprint. Note the _external=True is added to all url_for() calls so that fully qualified URLs are returned instead of the relative URLs that are typically used within the context of a traditional web application.
This example also shows how it is possible to return "made-up" attributes in the representation of a resource. The comment_conut field return the number of comments that exist for the blog post. Although this is not a real attribute of the model, it is included in the resource representation as a convenience to the client.
The to_json() method for User models can be constructed in a similar way to Post.
Of course, how in this method some of the attributes of user, such as email and role, are omitted from the response for privacy reasons.
This example demonstrates that the representation of a resource offered to clients does not need to be identical to the internal representation of corresponding database model.
one-to-many relationship field also can return list resource:
class Post(db.Model):
# ...
tags = db.relationship(# ...)
def to_json():
return {
# ...
'tags' = [{
'tag_url': tag.url,
'tag_name': tag.name
} for tag in self.tags ]
}
What remains is to implement the routes that handle the different resources. The GET requests are typically the easiest because they just return information and don’t need to make any changes.
Two GET handlers for blog posts.
@api.route('/posts/')
@auth.login_required
def get_posts():
posts = Post.query.all()
return jsonify({ 'posts': [post.to_json() for post in posts] })
@api.route('/posts/<int:id>')
@auth.login_required
def get_post(id):
post = Post.query.get_or_404(id)
return jsonify(post.to_json())
from_json
Converting a JSON structure to a model presents the challenge that some of the data coming from the client might be invalid, wrong, or unnecessary.
Create a blog post from JSON:
from app.exceptions import ValidationError
class Post(db.Model):
# ...
@staticmethod
def from_json(json_post):
body = json_post.get('body')
if body is None or body == '':
raise ValidationError('post does not have a body')
return Post(body=body)