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/'
        strict = True

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'
        strict = True

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'
        strict = True

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'
        strict = True

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'
        strict = True

class UserSchema(Schema):
    id = fields.Str(dump_only=True)
    name = fields.Str()

    class Meta:
        type_ = 'users'
        strict = True

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'
        strict = True

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'
        strict = True

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'
        strict = True

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.