Map objects to maps or json without part files or any nonsense! Just an annotation!
flutter pub add artifact
flutter pub add artifact_gen --dev
Make the model class
@artifact // Artifact is all you need!
class Animal {
@rename("hp")
final int health;
String? someNonSerializableField;
// Parameters define what is mapped
// Default values are used if not set
// Required parameters will throw if not set
const Animal({this.health = 100});
}
Let's add two subclasses for animals
@artifact
class Dog extends Animal {
final bool goodBoy;
const Dog({@rename("hp") super.health = 125, this.goodBoy = true});
}
@artifact
class Cat extends Animal {
final int lives;
const Cat({@rename("hp") super.health = 70, this.lives = 9});
}
Lets put this in a world object
@artifact
class World {
final List<Animal> animals;
const World({this.animals = const []});
}
Then use it!
World world = World(
animals: [
Dog(goodBoy: true),
Cat(lives: 8, health: 50),
]
);
print(jsonEncode(world.toMap()));
{
"animals": [
{
"_subclass_Animal": "Dog",
"hp": 125,
"goodBoy": true
},
{
"_subclass_Animal": "Cat",
"hp": 50,
"lives": 8
}
]
}
CopyWith also exists since artifacts are immutable
You deserialize with the generated extension
Animal animal = $Animal.fromMap(...)
// Add 10 hp
.copyWith(deltaHp: 10);
if(animal is Cat) {
// Add a life
(animal as Cat).copyWith(deltaLives: 1);
}
Attachments
@attach("Any const object")
@artifact
class SomeModel {
@attach(42)
final String name;
@attach(const SomeClassData())
final int age;
const SomeModel({
this.name = "John Doe",
this.age = 0,
});
}
Then you can get via attachments
List<dynamic> all = $SomeModel.rootAttachments;
// Or get field by attachment
SomeModel m = SomeModel(name: "Dan", age: 2);
String n = m.getAttachment<int, String>(42);
// n = "Dan
Custom Attachments
You can simply extend the attachment class to make it simpler to work with and utilize
// First we define a data class or enum to signal information
enum UiComponentType {
title,
subtitle,
}
// Then we extend annotations on attachment
class UITitle extends attachment{
const UITitle(super.data = UiComponentType.title);
}
class UISubtitle extends attachment{
const UIComponent(super.data = UiComponentType.subtitle);
}
// These are optional if you want to skip the ()
const uititle = UITitle();
const uisubtitle = UISubtitle();
Then we can use it in our models as annotations
@artifact
class Person {
@uititle
final String name;
@uisubtitle
final String email;
const Person({required this.name, required this.email});
}
Now, we can use it!
List<User> users = ...
Widget build(BuildContext context) => ListView(children: [
...users.map((i) => ListTile(
title: Text(i.getAttachment(UiComponentType.title)),
subtitle: Text(i.getAttachment(UiComponentType.subtitle)),
))
]);