Quickstart¶
Note
The following guide assumes some familiarity with the marshmallow API. To learn more about marshmallow, see its official documentation at https://marshmallow.readthedocs.io.
Declaring schemas¶
Let’s start with a basic post “model”.
class Post:
def __init__(self, id, title):
self.id = id
self.title = title
Declare your schemas as you would with marshmallow.
A Schema
MUST define:
An
id
fieldThe
type_
class Meta option
It is RECOMMENDED to set strict mode to True
.
Automatic self-linking is supported through these Meta options:
self_url
specifies the URL to the resource itselfself_url_kwargs
specifies replacement fields forself_url
self_url_many
specifies the URL the resource when a collection (many) are serialized
from marshmallow_jsonapi import Schema, fields
class PostSchema(Schema):
id = fields.Str(dump_only=True)
title = fields.Str()
class Meta:
type_ = "posts"
self_url = "/posts/{id}"
self_url_kwargs = {"id": "<id>"}
self_url_many = "/posts/"
These URLs can be auto-generated by specifying self_view
, self_view_kwargs
and self_view_many
instead when using the Flask integration.
Serialization¶
Objects will be serialized to JSON API documents with primary data.
post = Post(id="1", title="Django is Omakase")
PostSchema().dump(post)
# {
# 'data': {
# 'id': '1',
# 'type': 'posts',
# 'attributes': {'title': 'Django is Omakase'},
# 'links': {'self': '/posts/1'}
# },
# 'links': {'self': '/posts/1'}
# }
Relationships¶
The Relationship
field is used to serialize relationship objects. For example, a Post may have an author and comments associated with it.
class User:
def __init__(self, id, name):
self.id = id
self.name = name
class Comment:
def __init__(self, id, body, author):
self.id = id
self.body = body
self.author = author
class Post:
def __init__(self, id, title, author, comments=None):
self.id = id
self.title = title
self.author = author # User object
self.comments = [] if comments is None else comments # Comment objects
To serialize links, pass a URL format string and a dictionary of keyword arguments. String arguments enclosed in < >
will be interpreted as attributes to pull from the object being serialized. The relationship links can automatically be generated from Flask view names when using the Flask integration.
class PostSchema(Schema):
id = fields.Str(dump_only=True)
title = fields.Str()
author = fields.Relationship(
self_url="/posts/{post_id}/relationships/author",
self_url_kwargs={"post_id": "<id>"},
related_url="/authors/{author_id}",
related_url_kwargs={"author_id": "<author.id>"},
)
class Meta:
type_ = "posts"
user = User(id="94", name="Laura")
post = Post(id="1", title="Django is Omakase", author=user)
PostSchema().dump(post)
# {
# 'data': {
# 'id': '1',
# 'type': 'posts',
# 'attributes': {'title': 'Django is Omakase'},
# 'relationships': {
# 'author': {
# 'links': {
# 'self': '/posts/1/relationships/author',
# 'related': '/authors/94'
# }
# }
# }
# }
# }
Resource linkages¶
You can serialize resource linkages by passing include_resource_linkage=True
and the resource type_
argument.
class PostSchema(Schema):
id = fields.Str(dump_only=True)
title = fields.Str()
author = fields.Relationship(
self_url="/posts/{post_id}/relationships/author",
self_url_kwargs={"post_id": "<id>"},
related_url="/authors/{author_id}",
related_url_kwargs={"author_id": "<author.id>"},
# Include resource linkage
include_resource_linkage=True,
type_="users",
)
class Meta:
type_ = "posts"
PostSchema().dump(post)
# {
# 'data': {
# 'id': '1',
# 'type': 'posts',
# 'attributes': {'title': 'Django is Omakase'},
# 'relationships': {
# 'author': {
# 'data': {'type': 'users', 'id': '94'},
# 'links': {
# 'self': '/posts/1/relationships/author',
# 'related': '/authors/94'
# }
# }
# }
# }
# }
Compound documents¶
Compound documents allow to include related resources into the request with the primary resource. In order to include objects, you have to define a Schema
for the respective relationship, which will be used to render those objects.
class PostSchema(Schema):
id = fields.Str(dump_only=True)
title = fields.Str()
comments = fields.Relationship(
related_url="/posts/{post_id}/comments",
related_url_kwargs={"post_id": "<id>"},
many=True,
include_resource_linkage=True,
type_="comments",
# define a schema for rendering included data
schema="CommentSchema",
)
author = fields.Relationship(
self_url="/posts/{post_id}/relationships/author",
self_url_kwargs={"post_id": "<id>"},
related_url="/authors/{author_id}",
related_url_kwargs={"author_id": "<author.id>"},
include_resource_linkage=True,
type_="users",
)
class Meta:
type_ = "posts"
class CommentSchema(Schema):
id = fields.Str(dump_only=True)
body = fields.Str()
author = fields.Relationship(
self_url="/comments/{comment_id}/relationships/author",
self_url_kwargs={"comment_id": "<id>"},
related_url="/comments/{author_id}",
related_url_kwargs={"author_id": "<author.id>"},
type_="users",
# define a schema for rendering included data
schema="UserSchema",
)
class Meta:
type_ = "comments"
class UserSchema(Schema):
id = fields.Str(dump_only=True)
name = fields.Str()
class Meta:
type_ = "users"
Just as with nested fields the schema
can be a class or a string with a simple or fully qualified class name. Make sure to import the schema beforehand.
Now you can include some data in a dump by specifying the include_data
argument (also supports nested relations via the dot syntax).
armin = User(id="101", name="Armin")
laura = User(id="94", name="Laura")
steven = User(id="23", name="Steven")
comments = [
Comment(id="5", body="Marshmallow is sweet like sugar!", author=steven),
Comment(id="12", body="Flask is Fun!", author=armin),
]
post = Post(id="1", title="Django is Omakase", author=laura, comments=comments)
PostSchema(include_data=("comments", "comments.author")).dump(post)
# {
# 'data': {
# 'id': '1',
# 'type': 'posts',
# 'attributes': {'title': 'Django is Omakase'},
# 'relationships': {
# 'author': {
# 'data': {'type': 'users', 'id': '94'},
# 'links': {
# 'self': '/posts/1/relationships/author',
# 'related': '/authors/94'
# }
# },
# 'comments': {
# 'data': [
# {'type': 'comments', 'id': '5'},
# {'type': 'comments', 'id': '12'}
# ],
# 'links': {
# 'related': '/posts/1/comments'
# }
# }
# }
# },
# 'included': [
# {
# 'id': '5',
# 'type': 'comments',
# 'attributes': {'body': 'Marshmallow is sweet like sugar!'},
# 'relationships': {
# 'author': {
# 'data': {'type': 'users', 'id': '23'},
# 'links': {
# 'self': '/comments/5/relationships/author',
# 'related': '/comments/23'
# }
# }
# }
# },
# {
# 'id': '12',
# 'type': 'comments',
# 'attributes': {'body': 'Flask is Fun!'},
# 'relationships': {
# 'author': {
# 'data': {'type': 'users', 'id': '101'},
# 'links': {
# 'self': '/comments/12/relationships/author',
# 'related': '/comments/101'
# }
# }
# },
#
# },
# {
# 'id': '23',
# 'type': 'users',
# 'attributes': {'name': 'Steven'}
# },
# {
# 'id': '101',
# 'type': 'users',
# 'attributes': {'name': 'Armin'}
# }
# ]
# }
Meta Information¶
The DocumentMeta
field is used to serialize
the meta object within a document’s “top level”.
from marshmallow_jsonapi import Schema, fields
class UserSchema(Schema):
id = fields.Str(dump_only=True)
name = fields.Str()
document_meta = fields.DocumentMeta()
class Meta:
type_ = "users"
user = {"name": "Alice", "document_meta": {"page": {"offset": 10}}}
UserSchema().dump(user)
# {
# "meta": {
# "page": {
# "offset": 10
# }
# },
# "data": {
# "id": "1",
# "type": "users"
# "attributes": {"name": "Alice"},
# }
# }
The ResourceMeta
field is used to serialize the meta object within a resource object.
from marshmallow_jsonapi import Schema, fields
class UserSchema(Schema):
id = fields.Str(dump_only=True)
name = fields.Str()
resource_meta = fields.ResourceMeta()
class Meta:
type_ = "users"
user = {"name": "Alice", "resource_meta": {"active": True}}
UserSchema().dump(user)
# {
# "data": {
# "type": "users",
# "attributes": {"name": "Alice"},
# "meta": {
# "active": true
# }
# }
# }
Errors¶
Schema.load()
and Schema.validate()
will return JSON API-formatted Error objects.
from marshmallow_jsonapi import Schema, fields
from marshmallow import validate, ValidationError
class AuthorSchema(Schema):
id = fields.Str(dump_only=True)
first_name = fields.Str(required=True)
last_name = fields.Str(required=True)
password = fields.Str(load_only=True, validate=validate.Length(6))
twitter = fields.Str()
class Meta:
type_ = "authors"
author_data = {
"data": {"type": "users", "attributes": {"first_name": "Dan", "password": "short"}}
}
AuthorSchema().validate(author_data)
# {
# 'errors': [
# {
# 'detail': 'Missing data for required field.',
# 'source': {
# 'pointer': '/data/attributes/last_name'
# }
# },
# {
# 'detail': 'Shorter than minimum length 6.',
# 'source': {
# 'pointer': '/data/attributes/password'
# }
# }
# ]
# }
If an invalid “type” is passed in the input data, an IncorrectTypeError
is raised.
from marshmallow_jsonapi.exceptions import IncorrectTypeError
author_data = {
"data": {
"type": "invalid-type",
"attributes": {
"first_name": "Dan",
"last_name": "Gebhardt",
"password": "verysecure",
},
}
}
try:
AuthorSchema().validate(author_data)
except IncorrectTypeError as err:
pprint(err.messages)
# {
# 'errors': [
# {
# 'detail': 'Invalid type. Expected "users".',
# 'source': {
# 'pointer': '/data/type'
# }
# }
# ]
# }
Inflection¶
You can optionally specify a function to transform attribute names. For example, you may decide to follow JSON API’s recommendation to use “dasherized” names.
from marshmallow_jsonapi import Schema, fields
def dasherize(text):
return text.replace("_", "-")
class UserSchema(Schema):
id = fields.Str(dump_only=True)
first_name = fields.Str(required=True)
last_name = fields.Str(required=True)
class Meta:
type_ = "users"
inflect = dasherize
UserSchema().dump(user)
# {
# 'data': {
# 'id': '9',
# 'type': 'users',
# 'attributes': {
# 'first-name': 'Dan',
# 'last-name': 'Gebhardt'
# }
# }
# }
Flask integration¶
marshmallow-jsonapi includes optional utilities to integrate with Flask.
A Flask-specific schema in marshmallow_jsonapi.flask
can be used to
auto-generate self-links based on view names instead of hard-coding URLs.
Additionally, the Relationship
field in the marshmallow_jsonapi.flask
module allows you to pass view names instead of path templates to generate
relationship links.
from marshmallow_jsonapi import fields
from marshmallow_jsonapi.flask import Relationship, Schema
class PostSchema(Schema):
id = fields.Str(dump_only=True)
title = fields.Str()
author = fields.Relationship(
self_view="post_author",
self_url_kwargs={"post_id": "<id>"},
related_view="author_detail",
related_view_kwargs={"author_id": "<author.id>"},
)
comments = Relationship(
related_view="post_comments",
related_view_kwargs={"post_id": "<id>"},
many=True,
include_resource_linkage=True,
type_="comments",
)
class Meta:
type_ = "posts"
self_view = "post_detail"
self_view_kwargs = {"post_detail": "<id>"}
self_view_many = "posts_list"
See here for a full example.