Generics topic
One of dart_mappables most unique and powerful features is the handling of generic classes.
You can easily define any type of generic classes, including
- classes with one or more generic type parameters
- classes with bounded type parameters
- generic classes using polymorphism
dart_mappables promise is that it just works, no matter what classes you throw at it.
Generic Decoding
Normally when decoding an object, you call the fromJson or fromMap method of its specific
mapper, like PersonMapper.fromJson() to get a Person, or CarMapper.fromJson() to get a car.
However dart_mappable also has the ability to decode an object based on a type parameter alone,
using the respective fromJson or fromMap methods on a MapperContainer. Those methods are
actually generic and take a single type parameter, with the complete signature being: T fromJson<T>(String json).
So it returns an object of whatever type you give it.
With this you can also do container.fromJson<Person>(...) and container.fromJson<Car>(...) to get a Person or
Car respectively.
Now you are probably asking where this container comes from. A MapperContainer is what makes dart_mappable work
internally, and for simple cases you usually don't need to worry about it.
If you are interested in
MapperContainers, read this topic.
There are two ways to get a container:
- Each mapper has its own container, accessible through
<ClassName>Mapper.container.
However this container only knows about classes related to the model it is defined for. So for
example the PersonMapper.container would succeed in doing PersonMapper.container.fromJson<Person>(..)
but fail when trying to do PersonMapper.container.fromJson<Car>(...).
- You can create arbitrary containers using the
MapperContainer()constructor.
A container created like this initially knows about no models, but you can link other containers to include their mappers.
void main() {
// initially knows no mappers, so container.fromJson<Person>() would fail
var container = MapperContainer();
// links mappers for [Person] and [Car] (and related models)
container.linkAll([PersonMapper.container, CarMapper.container]);
// now this works
Person person = container.fromJson<Person>(...);
Car car = container.fromJson<Car>(...);
}
The power of this feature might be more clear when seeing it in a generic context. Instead of providing
type arguments to the function statically (fromJson<MyClass>), you can use this in another generic
function like this:
final container = MapperContainer()..linkAll([...]);
T decodeModel<T>(String json) {
return container.fromJson<T>();
}
Combined Containers
When using generic decoding you will need a MapperContainer that knows about all types that
you could possibly be decoded using a generic type parameter.
Creating such a container could be very tedious since you potentially had to link all mappers in your project.
Therefore dart_mappable can generate this container for you. To do this annotate a library of yours
with the @MappableLib() annotation and set the createCombinedContainer property to true:
@MappableLib(
createCombinedContainer: true,
)
library main;
This will create a new <filename>.container.dart file that creates a single mainContainer combining
all discovered mappers.
You can control what mappers are included by setting the discoveryMode property to one of three values:
@MappableLib(
createCombinedContainer: true,
discoveryMode: DiscoveryMode.library, // or DiscoveryMode.directory, DiscoveryMode.package
)
library main;
libraryis the default and discovers all mappers in the current library,directorydiscover all mappers in the current or any subdirectory,packagediscover all models in the current package.
Typeless Serialization
Generic Decoding is already a very powerful and unique feature of dart_mappable. But you can
go a step further and serialize json with no static typing at all - which I call typeless serialization.
All that is needed is a special __type key in the json data. You can then do some magic like this:
void main() {
/// this needs to know about all involved classes, see the section above
var container = ...;
var json = '{"__type": "Person", "name": "Bob"}';
// notice the static type 'dynamic', no 'Person.fromJson' or 'container.fromJson<Person>' needed
dynamic object = container.fromJson(json);
// this is actually a [Person] instance
assert(object.runtimeType == Person);
}
This also works for generic classes, like
{'__type': 'Box<Content>'}.
To go full circle, dart_mappable can also include this type indicator for you when you encode a
class. For this notice that the toValue, toMap and toJson methods of a container are actually
also generic: dynamic container.toValue<T>(T value).
This is different to the
toMapandtoJsonmethods of the generated mixin. You have to use aMapperContainerdirectly.
Specifying this type parameter is by design redundant and not important for most cases. It only takes
effect when the static type T and the runtime type value.runtimeType are different, in which case
it will add the __type property.
void main() {
// this needs to know about all involved classes
//
// either use a combined container (see the section above) or a
// class-specific one, e.g. [MyClassMapper.container]
var container = ...;
// this will encode normal without '__type'
container.toValue<MyClass>>(myClassInstance);
container.toValue(MyClass()); // works because of type inference
container.toValue<MyGenericClass<int>>(myGenericClassInstance);
// this will add the '__type' property
container.toValue<dynamic>(myClassInstance);
container.toValue(someDynamicVariable); // where type inference does not work
container.toValue<MyGenericClass<dynamic>(
myGenericClassInstance); // because the instance has a specific type
}
Together this allows you to serialize and deserialize an object without even knowing its type:
void main() {
// this needs to know about all involved classes (see the snippet above)
var container = ...;
dynamic someObject = ...;
String json = container.toJson(someObject);
// do something in between, or even send the json between server and client
dynamic newObject = container.fromJson(json);
assert(someObject.runtimeType == newObject.runtimeType);
}
Classes
- MappableLib Configuration Polymorphism Generics
- Used to annotate a library to define default values.
- MapperContainer Generics Mapper Container
Enums
- DiscoveryMode Generics
-
How to discover mappers when
MappableLib.createLinkedContaineris true.