Custom Mappers topic
You can create custom mappers to support custom types that are not part of the generated code. You can create a custom mapper in two ways:
- Inherit from the low-level
MapperBase
andMapperElementBase
interfaces, or - inherit from the high-level
SimpleMapper
interface.
For most use-cases it is recommended to use the SimpleMapper
interface and only use the base interfaces when
required for more advanced uses.
Simple Mapper
Create a class extending SimpleMapper<T>
with T
being the type that you want to decode to / encode from.
Then implement the decode()
and encode()
methods.
A custom mapper for the Uri
type would look like this:
class UriMapper extends SimpleMapper<Uri> {
const UriMapper();
@override
Uri decode(dynamic value) {
return Uri.parse(value as String);
}
@override
dynamic encode(Uri self) {
return self.toString();
}
}
In the encode()
method you should return a primitive serializable type, like String
, int
,
bool
, etc. or a List
or Map
of those types. There are also additional methods you can override,
like stringify
, or equals
. This will enable ==
checks and toString
on classes using this type.
Usage
To use a custom mapper, add it to the MappableClass.includeCustomMappers
annotation property of the target
class:
@MappableClass(includeCustomMappers: [UriMapper()])
class Domain {
final Uri uri;
const Domain(this.uri);
}
This will globally register the custom mapper when the class is initialized and is afterwards available for all models.
Alternatively you can manually add this to the MapperContainer.globals
:
void main() {
// Add this as the global [Uri] mapper for all models.
MapperContainer.globals.use(UriMapper());
}
Tip: Be aware that you can also unuse()
(and replace) any mappers, both custom, generated, and for primitive types.
This might come in handy if you want to switch between different custom mappers for the same type.
Generic Custom Types
When dealing with generic types with one or two type parameters, you can use the SimpleMapper1
or SimpleMapper2
variants. Here the decoding and encoding functions are generic functions for
receiving the correct type parameters:
You also need to construct a typeFactory
as shown below.
class GenericBox<T> {
T content;
GenericBox(this.content);
}
class CustomBoxMapper extends SimpleMapper1<GenericBox> {
@override
// use the type parameter [T] in the return type [GenericBox<T>]
GenericBox<T> decode<T>(dynamic value) {
// use the type parameter [T] in your decoding logic
T content = container.fromValue<T>(value);
return GenericBox<T>(container.fromValue<T>(value));
}
@override
// use the type parameter [T] in the parameter type [GenericBox<T>]
dynamic encode<T>(GenericBox<T> self) {
// use the type parameter [T] in your encoding logic
return container.toValue<T>(self.content);
}
// In case of generic types, we also must specify a type factory. This is a special type of
// function used internally to construct generic instances of your type.
// Specify any type arguments in alignment to the decode/encode functions.
@override
Function get typeFactory => <T>(f) => f<GenericBox<T>>();
}
If your generic class has one or two bounded type parameters (e.g. class NumberBox<T extends num>
) use the SimpleMapper1Bounded
and SimpleMapper2Bounded
variants:
class NumberBox<T extends num> {
NumberBox(this.content);
final T content;
}
class NumberBoxMapper extends SimpleMapper1Bounded<NumberBox, num> {
const NumberBoxMapper();
@override
NumberBox<A> decode<A extends num>(Object value) {
return NumberBox<A>(container.fromValue<A>(value));
}
@override
Object? encode<A extends num>(NumberBox<A> self) {
return container.toValue(self.content);
}
@override
Function get typeFactory => <T extends num>(f) => f<NumberBox<T>>();
}
Custom Iterables and Maps
For special Iterable and Map types, you can of course specify custom mappers as described in the previous section.
However, we provide ready-to-use IterableMapper
and MapMapper
to make your life a little bit easier:
For both you have to provide
- a factory function, which converts a generic iterable / map to your special implementation,
- a type factory, similar to the one used in generic custom mappers.
void main() {
MapperContainer.globals.use(IterableMapper<HashSet>(
<T>(Iterable<T> i) => HashSet.of(i),
<T>(f) => f<HashSet<T>>(),
));
MapperContainer.globals.use(MapMapper<HashMap>(
<K, V>(Map<K, V> m) => HashMap.of(m),
<K, V>(f) => f<HashMap<K, V>>(),
));
HashSet<String> brands = MapperContainer.globals.fromJson('["Toyota", "Audi", "Audi"]');
print(String); // {"Toyota", "Audi"}
}
Advanced Uses
For more advanced uses, extend from the MapperBase
class instead of the SimpleMapper
.
Here the setup requires a bit more boilerplate, but you get full control over your types:
// extend [MapperBase] and provide your type
class MyClassMapper extends MapperBase<MyClass> {
// If your type is generic, specify a type factory. Else this can be skipped.
@override
Function get typeFactory => <T>(f) => f<MyClass<T>>();
// all the following overrides are optional
@override
MyClass decoder(DecodingContext<Object> context) {
// your decoding logic here
// you can access the encoded value using 'context.value'
throw UnimplementedError();
}
@override
Object? encoder(EncodingContext<Object> context) {
// your encoding logic here
// you can access the decoded value using 'context.value'
throw UnimplementedError();
}
@override
bool equals(MappingContext<Object> context, MyClass other) {
// your equals logic here
throw UnimplementedError();
}
@override
int hash(MappingContext<Object> context) {
// your hashcode logic here
throw UnimplementedError();
}
@override
String stringify(MappingContext<Object> context) {
// your stringify logic here
throw UnimplementedError();
}
}
Classes
-
EnumMapper<
T extends Enum> Custom Mappers - The mapper interface used for all concrete enum mappers.
-
IterableMapper<
I extends Iterable> Custom Mappers - The default mapper for iterables like List.
-
MapMapper<
M extends Map> Custom Mappers - The default mapper for Maps.
- MappableClass Introduction Models Configuration Polymorphism Mapping Hooks Custom Mappers
- Used to annotate a class in order to generate mapping code.
-
MapperBase<
T extends Object> Custom Mappers - The common super class for all mappers.
-
SetMapper<
S extends Set> Custom Mappers - The default mapper for Sets.
-
SimpleMapper<
T extends Object> Custom Mappers - An interface to define a custom mapper.
-
SimpleMapper1<
T extends Object> Custom Mappers - An interface to define custom mappers for generic types with one argument.
-
SimpleMapper1Bounded<
T extends Object, B1> Custom Mappers - An interface to define custom mappers for generic types with one bounded argument.
-
SimpleMapper2<
T extends Object> Custom Mappers - An interface to define custom mappers for generic types with two arguments.
-
SimpleMapper2Bounded<
T extends Object, B1, B2> Custom Mappers - An interface to define custom mappers for generic types with two bounded arguments.
Enums
- IterableEqualityMode Custom Mappers
- Options for comparing an Iterable.