JSON Serialization of Stores
The package is a popular way to
encode/decode between json representations of your models. It works by attaching
the
@JsonSerializable()
annotation to the Store
classes. Since this is a
custom annotation, we have to invoke the build_runner
command, just like we do
for mobx_codegen
.
Let's add support for json_serializable
to the todos example.
See the complete code here.
Adding dependency in pubspec.yaml
The first step is to include the dependency on the
dependencies:
json_serializable: ^6.3.2
json_annotation: ^4.6.0
Adding annotations
To make our store classes travel to JSON and back, we need to annotate them with
@JsonSerializable()
:
import 'package:json_annotation/json_annotation.dart';
import 'package:mobx/mobx.dart';
part 'todo.g.dart';
()
class Todo extends _Todo with _$Todo {
Todo(String description) : super(description);
}
enum VisibilityFilter { all, pending, completed }
()
class TodoList extends _TodoList with _$TodoList {}
abstract class _TodoList with Store {
()
ObservableList<Todo> todos = ObservableList<Todo>();
VisibilityFilter filter = VisibilityFilter.all;
String currentDescription = '';
ObservableList<Todo> get pendingTodos =>
ObservableList.of(todos.where((todo) => todo.done != true));
ObservableList<Todo> get completedTodos =>
ObservableList.of(todos.where((todo) => todo.done == true));
bool get hasCompletedTodos => completedTodos.isNotEmpty;
bool get hasPendingTodos => pendingTodos.isNotEmpty;
String get itemsDescription {
if (todos.isEmpty) {
return "There are no Todos here. Why don't you add one?.";
}
final suffix = pendingTodos.length == 1 ? 'todo' : 'todos';
return '${pendingTodos.length} pending $suffix, ${completedTodos.length} completed';
}
(ignore: true)
ObservableList<Todo> get visibleTodos {
switch (filter) {
case VisibilityFilter.pending:
return pendingTodos;
case VisibilityFilter.completed:
return completedTodos;
default:
return todos;
}
}
bool get canRemoveAllCompleted =>
hasCompletedTodos && filter != VisibilityFilter.pending;
bool get canMarkAllCompleted =>
hasPendingTodos && filter != VisibilityFilter.completed;
void addTodo(String description) {
final todo = Todo(description);
todos.add(todo);
currentDescription = '';
}
void removeTodo(Todo todo) {
todos.removeWhere((x) => x == todo);
}
void removeCompleted() {
todos.removeWhere((todo) => todo.done);
}
void markAllAsCompleted() {
for (final todo in todos) {
todo.done = true;
}
}
}
Custom Converters
In case of special types like ObservableList<Todo>
, which are not directly
serializable into JSON, we can write custom converters by extending the
JsonConverter<T, S>
type, as shown below:
import 'package:json_annotation/json_annotation.dart';
import 'package:mobx/mobx.dart';
import 'package:mobx_examples/todos/todo.dart';
class ObservableTodoListConverter extends JsonConverter<ObservableList<Todo>,
Iterable<Map<String, dynamic>>> {
const ObservableTodoListConverter();
ObservableList<Todo> fromJson(Iterable<Map<String, dynamic>> json) =>
ObservableList.of(json.map(Todo.fromJson));
Iterable<Map<String, dynamic>> toJson(ObservableList<Todo> object) =>
object.map((element) => element.toJson());
}
This converter is then used for the todos
property as shown below:
abstract class _TodoList with Store {
()
ObservableList<Todo> todos = ObservableList<Todo>();
// ...
}
On with the code-generation
With these changes, let's run the build_runner
command in the project folder:
dart pub run build_runner watch --delete-conflicting-outputs
This will generate todo.g.dart
and todo_list.g.dart
files.
JSON Serialization / Deserialization
final list = TodoList();
expect(list.todos.length, equals(0));
list..addTodo('first one')..addTodo('second one');
const targetJson = '''
{
"todos": [
{
"description": "first one",
"done": false
},
{
"description": "second one",
"done": false
}
],
"filter": "VisibilityFilter.all",
}''';
final listJson = list.toJson();
expect(listJson, targetJson);
final listInstance = TodoList.fromJson(listJson);
expect(list.todos.length, listInstance.todos.length);
expect(list.canMarkAllCompleted, listInstance.canMarkAllCompleted);
expect(list.itemsDescription, listInstance.itemsDescription);
Summary
With these changes, you should now be able to serialize the Todos to/from
JSON
✌️. BTW, its worth noting that mobx_codegen
can co-exist with other
generators.
See the complete code here.