Custom fields¶
There are three ways to create a custom-formatted field for a Schema:
The method you choose will depend on the manner in which you intend to reuse the field.
Creating a field class¶
To create a custom field class, create a subclass of marshmallow.fields.Field and implement its _serialize and/or _deserialize methods.
Field’s type argument is the internal type, i.e. the type that the field deserializes to.
from marshmallow import fields, ValidationError
class PinCode(fields.Field[list[int]]):
"""Field that serializes to a string of numbers and deserializes
to a list of numbers.
"""
def _serialize(self, value, attr, obj, **kwargs):
if value is None:
return ""
return "".join(str(d) for d in value)
def _deserialize(self, value, attr, data, **kwargs):
try:
return [int(c) for c in value]
except ValueError as error:
raise ValidationError("Pin codes must contain only digits.") from error
class UserSchema(Schema):
name = fields.String()
email = fields.String()
created_at = fields.DateTime()
pin_code = PinCode()
Method fields¶
A Method field will serialize to the value returned by a method of the Schema. The method must take an obj parameter which is the object to be serialized.
class UserSchema(Schema):
name = fields.String()
email = fields.String()
created_at = fields.DateTime()
since_created = fields.Method("get_days_since_created")
def get_days_since_created(self, obj):
return dt.datetime.now().day - obj.created_at.day
Function fields¶
A Function field will serialize the value of a function that is passed directly to it. Like a Method field, the function must take a single argument obj.
class UserSchema(Schema):
name = fields.String()
email = fields.String()
created_at = fields.DateTime()
uppername = fields.Function(lambda obj: obj.name.upper())
Method and Function field deserialization¶
Both Function and Method receive an optional deserialize argument which defines how the field should be deserialized. The method or function passed to deserialize receives the input value for the field.
class UserSchema(Schema):
# `Method` takes a method name (str), Function takes a callable
balance = fields.Method("get_balance", deserialize="load_balance")
def get_balance(self, obj):
return obj.income - obj.debt
def load_balance(self, value):
return float(value)
schema = UserSchema()
result = schema.load({"balance": "100.00"})
result["balance"] # => 100.0
Using context¶
A field may need information about its environment to know how to (de)serialize a value.
You can use the experimental Context class
to set and retrieve context.
Let’s say your UserSchema needs to output
whether or not a User is the author of a Blog or
whether a certain word appears in a Blog's title.
import typing
from dataclasses import dataclass
from marshmallow import Schema, fields
from marshmallow.experimental.context import Context
@dataclass
class User:
name: str
@dataclass
class Blog:
title: str
author: User
class ContextDict(typing.TypedDict):
blog: Blog
class UserSchema(Schema):
name = fields.String()
is_author = fields.Function(
lambda user: user == Context[ContextDict].get()["blog"].author
)
likes_bikes = fields.Method("writes_about_bikes")
def writes_about_bikes(self, user: User) -> bool:
return "bicycle" in Context[ContextDict].get()["blog"].title.lower()
Note
You can use Context.get
within custom fields, pre-/post-processing methods, and validators.
When (de)serializing, set the context by using Context as a context manager.
user = User("Freddie Mercury", "fred@queen.com")
blog = Blog("Bicycle Blog", author=user)
schema = UserSchema()
with Context({"blog": blog}):
result = schema.dump(user)
print(result["is_author"]) # => True
print(result["likes_bikes"]) # => True
Customizing error messages¶
Validation error messages for fields can be configured at the class or instance level.
At the class level, default error messages are defined as a mapping from error codes to error messages.
from marshmallow import fields
class MyDate(fields.Date):
default_error_messages = {"invalid": "Please provide a valid date."}
Note
A Field's default_error_messages dictionary gets merged with its parent classes’ default_error_messages dictionaries.
Error messages can also be passed to a Field's constructor.
from marshmallow import Schema, fields
class UserSchema(Schema):
name = fields.Str(
required=True, error_messages={"required": "Please provide a name."}
)
Next steps¶
Need to add schema-level validation, post-processing, or error handling behavior? See the Extending schemas page.
For example applications using marshmallow, check out the Examples page.