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 field

  • The 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 itself

  • self_url_kwargs specifies replacement fields for self_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.