<h1 align="center">dart_mappable</h1>

<p align="center">
  <a href="https://pub.dev/packages/dart_mappable">
    <img src="https://img.shields.io/pub/v/dart_mappable?label=pub.dev&labelColor=333940&logo=dart&color=00589B">
  </a>
  <a href="https://github.com/schultek/dart_mappable/actions/workflows/test.yaml">
    <img src="https://img.shields.io/github/actions/workflow/status/schultek/dart_mappable/test.yaml?branch=main&label=tests&labelColor=333940&logo=github">
  </a>
  <a href="https://app.codecov.io/gh/schultek/dart_mappable">
    <img src="https://img.shields.io/codecov/c/github/schultek/dart_mappable?logo=codecov&logoColor=fff&labelColor=333940">
  </a>
  <br/>
  <a href="https://twitter.com/schultek_dev">
    <img src="https://img.shields.io/badge/follow-%40schultek__dev-1DA1F2?style=flat&label=follow&color=1DA1F2&labelColor=333940&logo=twitter&logoColor=fff">
  </a>
  <a href="https://github.com/schultek/dart_mappable">
    <img src="https://img.shields.io/github/stars/schultek/dart_mappable?style=flat&label=stars&labelColor=333940&color=8957e5&logo=github">
  </a>
</p>

<p align="center">
  <a href="#quick-start">Quickstart</a> •
  <a href="https://pub.dev/documentation/dart_mappable/latest/topics/Introduction-topic.html">Documentation</a> •
  <a href="https://pub.dev/packages/dart_mappable/example">Example</a> •
  <a href="https://github.com/schultek/dart_mappable/tree/main/examples/package_comparison">Package Comparison</a>
</p>

<p align="center">
  <b>Improved json serialization and data classes</b> with <br/> full support for <b>generics, inheritance, customization and more</b>. 
</p>

---

`dart_mappable` covers all basic features *(from/to json, == override, hashCode, toString(), copyWith)*
while adding new or improved support for advanced use-cases including **generics, inheritance and 
polymorphism, customization** and more.

- 🎁 **Everything included**: Serialization, Equality, ToString, CopyWith and more.
- 🚀 **Excels at complexity**: It handles generics, polymorphism and multi-inheritance with ease.
- 🎛️ **Highly flexible**: Customize the serialization, add custom types or integrate with other packages.
- 🔥 **No compromises**: Its promise is that it just works, no matter what classes you throw at it.  
  *(If you find an unsupported case, you get a cookie 🍪. And please add an issue on [github](https://github.com/schultek/dart_mappable).)*

## Quick Start

First, add `dart_mappable` as a dependency, together with `dart_mappable_builder` and `build_runner` as a dev_dependency.

```shell script
flutter pub add dart_mappable
flutter pub add build_runner --dev
flutter pub add dart_mappable_builder --dev
```

---

Next annotate your classes that you want to use with `@MappableClass()` and add the
appropriate `part` directive to include the generated `.mapper.dart` file:

```dart
// This file is "model.dart"
import 'package:dart_mappable/dart_mappable.dart';

// Will be generated by dart_mappable
part 'model.mapper.dart';

@MappableClass()
class MyClass with MyClassMappable {
  final int myValue;

  MyClass(this.myValue);
}
```

To use a class you must:

- annotate the class with `@MappableClass()` and
- apply a mixin with the name of the class plus `Mappable`.

***Tip**: Don't worry if the mixin don't exist at first, just run code-generation once and it will be created.
The builder will also warn you if you define your class without the proper mixin.*

***Note**: For generic classes (e.g. `MyClass<T>`) make sure to also provide all type parameters
to the mixin (`... with MyClassMappable<T>`).*

---

In order to generate the serialization code, run the following command:

```shell script
dart run build_runner build
```

***Tip**: You'll need to re-run code generation each time you are making changes to your annotated classes.
During development, you can use `watch` to automatically watch your changes: `dart run build_runner watch`.*

This will generate a `<filename>.mapper.dart` file for each of your files containing annotated classes.

---

Last step is to use the generated mappers. There are two main ways to interact with your models
using this package:

1. Through the generated `<ClassName>Mapper` classes, and
2. through the methods defined by the generated mixin.

```dart
...

void main() {
  // Decode a [Map] using the [MyClassMapper] class:
  MyClass myClass = MyClassMapper.fromMap({'myValue': 123});
  
  // Or decode directly from json:
  MyClass myClass2 = MyClassMapper.fromJson('{"myValue": 123}');
  
  // Encode an instance of your class using the methods provided by the mixin:
  Map<String, dynamic> map = myClass.toMap();
  String json = myClass.toJson();
  
  // There are also implementations generated for [operator ==], [hashCode] and [toString]:
  bool thisIsTrue = (myClass == myClass2);
  print(myClass);
  
  // Last you can use [copyWith] to create a copy of an object:
  MyClass myClass3 = myClass.copyWith(myValue: 0);
}
```

***Beware**: The `.toJson()` method returns a `String`. If you are migrating from `json_serializable`, you might
be used to this returning a `Map<String, dynamic>` instead. Make sure to properly adapt your code to this change, 
as not doing so might lead to unexpected behavior. Find more about the recommended migration path 
[here](https://pub.dev/documentation/dart_mappable/latest/topics/Migration%20and%20Compatibility-topic.html)*.

## Overview

To setup, annotate your model classes with `@MappableClass()` and your enums with `@MappableEnum()`.
Each annotation has a set of properties to configure the generated code.

```dart
@MappableClass()
class MyClass with MyClassMappable { ... }

@MappableEnum()
enum MyEnum { ... }
```

***Tip**: Check out the documentation about
[Models](https://pub.dev/documentation/dart_mappable/latest/topics/Models-topic.html) and
[Enums](https://pub.dev/documentation/dart_mappable/latest/topics/Enums-topic.html).*

For deserialization, `dart_mappable` will use the first available constructor of a class, but you
can use a specific constructor using the `@MappableConstructor()` annotation.

```dart
@MappableClass()
class MyClass with MyClassMappable {
  MyClass(); // Don't use this

  @MappableConstructor()
  MyClass.special(); // Use this
}
```

You can also annotate a single field or constructor parameter of a class using `@MappableField()`
to set a specific json key or add custom hooks.

```dart
@MappableClass()
class MyClass with MyClassMappable {
  MyClass(this.value);

  @MappableField(key: 'my_key')
  String value;
}
```

***Note**: This can only be used on a field if it is directly assigned as a constructor parameter (`MyClass(this.myField)`).
Setting this annotation on any other field will have no effect.
(Read [Utilizing Constructors](https://pub.dev/documentation/dart_mappable/latest/topics/Models-topic.html#utilizing-constructors) for an explanation why this is.)*

***Tip**: Hooks are a way to customize the serialization of any field or class.
Read more in the documentation about [Mapping Hooks](https://pub.dev/documentation/dart_mappable/latest/topics/Mapping%20Hooks-topic.html).*

You can add the `@MappableLib()` annotation to your `library` statement to set a default configuration
for all included classes and enums, e.g. the case style for json keys.

```dart
@MappableLib(caseStyle: CaseStyle.camelCase) // will be applied to all classes
library model;

part 'model.mapper.dart';

@MappableClass() // no need to set caseStyle here
class MyClass with MyClassMappable {
  ...
}
```

***Tip**: Check out the documentation to see all available [Configuration](https://pub.dev/documentation/dart_mappable/latest/topics/Configuration-topic.html) options.*

---

Here are again all **six** annotations that you can use in your code:

1. `@MappableClass()` can be used on a class to specify options like the `caseStyle` of the json keys, whether to ignore null values, or [hooks](https://pub.dev/documentation/dart_mappable/latest/topics/Mapping%20Hooks-topic.html).
2. `@MappableConstructor()` can be used on a constructor to mark this to be used for decoding. It has no properties.
3. `@MappableField()` can be used on a constructor parameter or a field to specify a json key to be used instead of the field name, or [hooks](https://pub.dev/documentation/dart_mappable/latest/topics/Mapping%20Hooks-topic.html).
4. `@MappableEnum()` can be used on an enum to specify the `mode` or `caseStyle` of the encoded enum values, or the `defaultValue`.
5. `@MappableValue()` can be used on an enum value to specify a custom encoded value to use.
6. `@MappableLib()` can be used on a library statement or import / export statement to set a default configuration for the annotated library or include / exclude classes.

### Mapper Interface

`dart_mappable` will generate `Mapper` classes that provide these methods or properties:

- `<ClassName>Mapper.fromMap<T>(Map<String, dynamic> map)` will take an encoded map object and return a decoded object of type `ClassName`.
- `<ClassName>Mapper.fromJson<T>(String json)` internally uses `fromMap` but works with json encoded `String`s.

***Tip**: If you prefer to use `MyClass.fromJson` over `MyClassMapper.fromJson`, add the `fromJson` and
`fromMap` methods directly to your class like this:*

```
class MyClass with MyClassMappable {
  ...

 static final fromMap = MyClassMapper.fromMap;
 static final fromJson = MyClassMapper.fromJson;
}
```

The generated `<ClassName>Mappable` mixin will come with the following methods:

- `toMap()` and `toJson()`.
- `copyWith()` to create copies of your class instance (see [Copy With](https://pub.dev/documentation/dart_mappable/latest/topics/Copy-With-topic.html)).
- overrides for `operator ==`, `hashCode` and `toString()`.

## Full Documentation

See the full documentation [here](https://pub.dev/documentation/dart_mappable/latest/topics/Introduction-topic.html)
or jump directly to the topic you are looking for:

- [**Models**](https://pub.dev/documentation/dart_mappable/latest/topics/Models-topic.html) 
  show you how to structure and annotate your data models.
- [**Enums**](https://pub.dev/documentation/dart_mappable/latest/topics/Enums-topic.html) 
  show you how to structure and annotate your enums.
- [**Records**](https://pub.dev/documentation/dart_mappable/latest/topics/Records-topic.html)
  show you how to use records as part of your models.
- [**Configuration**](https://pub.dev/documentation/dart_mappable/latest/topics/Configuration-topic.html) 
  goes into the different configuration options you have.
- [**Copy-With**](https://pub.dev/documentation/dart_mappable/latest/topics/Copy-With-topic.html) 
  describes the copy-with functionalities and how to use them.
- [**Polymorphism**](https://pub.dev/documentation/dart_mappable/latest/topics/Polymorphism-topic.html) 
  shows how to do polymorphic classes and inheritance.
- [**Generics**](https://pub.dev/documentation/dart_mappable/latest/topics/Generics-topic.html) 
  explain generic decoding and how to use it.
- [**Mapping Hooks**](https://pub.dev/documentation/dart_mappable/latest/topics/Mapping%20Hooks-topic.html) 
  shows how to use hooks to customize your de- and encoding process. 
- [**Custom Mappers**](https://pub.dev/documentation/dart_mappable/latest/topics/Custom%20Mappers-topic.html) 
  explains how to set up and use (non-generated) custom mappers.
- [**Mapper Container**](https://pub.dev/documentation/dart_mappable/latest/topics/Mapper%20Container-topic.html) 
  describes the inner workings of mapper containers in more detail.
- [**Migration and Compatibility**](https://pub.dev/documentation/dart_mappable/latest/topics/Migration%20and%20Compatibility-topic.html) 
  shows you how you can incrementally migrate from other packages like freezed or json_serializable and use compatible 
  packages like fast_immutable_collections.
