`MapperContainer`s are the backbone of `dart_mappable`. A `MapperContainer`s job is to lookup the correct
mapper for a type and call its respective method.

To find the mapper for a given type, the container first looks at its own **set of mappers** and when 
there is no match it refers to its **linked containers**.

---
# 🚨 Important!

**A lot of this may be removed in a future version.**
See https://github.com/schultek/dart_mappable/issues/159 for more details.

---

## Working with `MapperContainer`s

**This is an advanced topic.** Usually you don't need to worry about containers, as they are hidden from the user when doing standard
things like decoding or encoding from json or using the generated mixins `toString`, `==` or `hashCode` overrides.

*You don't need to know this for most use-cases. But feel free to read on if you are interested.*

By default there is only the globally accessible `MapperContainer.globals`. The interface of a container contains all the respective methods for 
serialization and mapping, like `fromJson`, `toJson`, `isEqual`, `hash` and `asString`. You can use these methods with any object as long
as the container can resolve a mapper for it.

To control which types a `MapperContainer` can resolve see the following methods:

- `use<T>(MapperBase<T> mapper)` will add a new mapper for type `T`.
- `unuse<T>()` will remove the current mapper for type `T`. *Note that this works for any mapper, custom, generated or for primitive types.*
- `useAll(Iterable<MapperBase> mapper)` works as `use()` but for a list of mappers.

- `get<T>()` will return the current mapper for type `T`.
- `getAll()` will return all currently registered mappers.

The `MapperContainer.global` has a default set of mappers for all core Dart types. When calling `MyClassMapper.ensureInitialized()`
on a generated mapper, it will add itself to the global container and from thereon be usable through it.

Additionally to using the preconfigured containers, you can also create one yourself using the `MapperContainer()` constructor.

In extension to controlling mappers manually for a single container, you can also link a container to 
another. That way container A can use all mappers of container B implicitly.

- `link(MapperContainer container)` links the provided container to the current one.

The following example shows this in action for a schematic setup of two classes and mappers.

```dart

// Suppose we have two classes and their respective mappers
class A {}
class B {}

class AMapper extends MapperBase<A> {} // generated
class BMapper extends MapperBase<B> {} // generated

void main() {
  
  AMapper.ensureInitialized();
  
  // succeeds - AMapper was added to the globals container.
  MapperContainer.globals.toJson(A());
  
  // fails - B is not initialized yet.
  MapperContainer.globals.toJson(A());

  BMapper.ensureInitialized();
  
  // creates a new empty container
  var containerX = MapperContainer();

  // fails - no mappers were added yet
  containerX.toJson(A());

  // succeeds - added the respective mapper
  containerX.use(AMapper());
  containerX.toJson(A());
  
  var containerY = MapperContainer();
  containerY.use(BMapper());

  // succeeds - added the respective mapper
  containerY.toJson(B());
  // fails - missing mapper for type A
  containerY.toJson(A());
  
  // links containerX to containerY
  containerY.link(containerX);

  // succeeds - mapper is resolved through linked containerX
  containerY.toJson(A());
  
  // fails - linking is one-directional
  containerX.toJson(B());
}
```

## Defaults Container

There is a special **global defaults** container that can be accessed using `MapperContainer.defaults`. This
container initially contains mappers for all **primitive and core types**. Types included are:

`dynamic`, `Object`, `String`, `int`, `double`, `num`, `bool`, `DateTime`, `List`, `Set`, `Map`

**All other containers are implicitly linked to this default container.**

You can use this container to add mappers for types, that are thereby supported by all other containers.

```dart
// suppose we have a class and a custom mapper for it
class MyClass {}
class MyClassMapper extends SimpleMapper<MyClass> { ... }

void main() {
  
  var container = MapperContainer();
  // this doen't work, since the mapper for type [MyClass] wasn't added yet to this container
  container.toJson(MyClass());
  
  // we add the mapper as a global default
  MapperContainer.defaults.use(MyClassMapper());

  // now this works, since all containers are linked to the default container
  container.toJson(MyClass());
}

```

## Globals Container

The second special container is the `MapperContainer.globals` container. This
container is used by all generated mappers for global registration.

A generated mapper adds itself to the globals container when calling `MyMapper.ensureInitialized()`.

## Encoding Lists, Sets and Maps

Because all of the `MapperContainer`s methods are generic, you are not limited to use a single 
object with these methods. `dart_mappable` also support `List`s, `Set`s and `Map`s out of the box, 
without any special syntax, workarounds or hacks.

```dart
@MappableClass()
class Dog with DogMappable {
  String name;
  Dog(this.name);
}

@MappableClass()
class Box<T> with BoxMappable<T> {
  T content;
  Box(this.content);
}

void main() {
  // Case 1: Simple list.
  // We use the default container since we only use core types.
  List<int> nums = MapperContainer.globals.fromJson('[2, 4, 105]');
  print(nums); // [2, 4, 105]

  // Case 2: Set of objects.
  // We use the generated container for [Dog], but with a set of objects.
  DogMapper.ensureInitialized();
  Set<Dog> dogs = MapperContainer.globals.fromJson('[{"name": "Thor"}, {"name": "Lasse"}, {"name": "Thor"}]');
  print(dogs); // {Dog(name: Thor), Dog(name: Lasse)}

  // Case 3: More complex lists, like generics.
  // We use the generated container for [Box], but with a list of generic objects.
  BoxMapper.ensureInitialized();
  List<Box<double>> boxes = MapperContainer.globals.fromJson('[{"content": 0.1}, {"content": 12.34}]');
  print(boxes); // [Box(content: 0.1), Box(content: 12.34)]
}
```

There is also the `MapperContainer.fromIterable` method. This can be used if you already have a list of dynamic objects instead of the raw json string.
Additionally this can get handy to decode a dynamic list of partly-encoded values:

```dart
List<double> myNumbers = MapperContainer.globals.fromIterable([2.312, '1.32', 500, '1e4']);
print(myNumbers); // [2.312, 1.32, 500.0, 10000.0]
```

## Non-Trivial Maps

We also support decoding of non-trivial maps.
Although the use-cases might be rare, you can decode to something other than a map of string keys like this:

```dart
var encodedMap = {
  {'name': 'Bonny'}: 1,
  {'name': 'Clyde'}: 5,
};

DogMapper.ensureInitialized();
Map<Dog, int> treatsPerDog = MapperContainer.globals.fromValue(encodedMap);
print(treatsPerDog[Dog('Clyde')]!); // 5

var myMap = MapperContainer.globals.toValue(treatsPerDog);
print(myMap); // {{name: Bonny}: 1, {name: Clyde}: 5}
```

***Note**: Make sure to add `@MappableClass` on your key class, in order to enable easy property access.*

***Note**: Since json only supports string keys, we can't do `fromJson` or `toJson` on these maps.
You would have to decode / encode your keys and values separately.*

---

*🎉 Congrats, you followed the tour until the end. Now you know everything about this package.*
