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.