Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding a detailed guide about json serialization.
- Loading branch information
1 parent
9f2391c
commit 749a996
Showing
3 changed files
with
176 additions
and
8 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
--- | ||
menu: Guides | ||
route: /guides/json | ||
name: JSON Serialization of Stores | ||
--- | ||
|
||
# JSON Serialization of Stores | ||
|
||
The [![pub package](https://img.shields.io/pub/v/json_serializable.svg?label=json_serializable&color=blue)](https://pub.dartlang.org/packages/json_serializable) 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`. After the code is generated, you will have the _to/from-Json_ methods created for you. | ||
|
||
Let's add support for `json_serializable` to the **todos** example. | ||
|
||
## Adding dependency in `pubspec.yaml` | ||
|
||
The first step is to include the dependency on the [![pub package](https://img.shields.io/pub/v/json_serializable.svg?label=json_serializable&color=blue)](https://pub.dartlang.org/packages/json_serializable) package. We add this to the `pubspec.yaml` and run `flutter packages get` to download it. | ||
|
||
```yaml | ||
dependencies: | ||
json_serializable: ^3.0.0 | ||
``` | ||
|
||
## Adding annotations | ||
|
||
To generate the json-serialization code, we need to annotate our Store classes: `Todo` and `TodoList`. In the code below, you can see these annotations, as well as some convenience methods (`toJson`, `fromJson`) that wrap the generated methods. | ||
|
||
```dart | ||
import 'package:json_annotation/json_annotation.dart'; | ||
import 'package:mobx/mobx.dart'; | ||
part 'todo.g.dart'; | ||
@JsonSerializable() | ||
class Todo extends _Todo with _$Todo { | ||
Todo(String description) : super(description); | ||
static Todo fromJson(Map<String, dynamic> json) => _$TodoFromJson(json); | ||
static Map<String, dynamic> toJson(Todo todo) => _$TodoToJson(todo); | ||
} | ||
``` | ||
|
||
> In the above code, the `_$TodoFromJson()` and `_$TodoToJson()` methods are generated by `json_serializable`. | ||
### Dealing with `ObservableList<T>` | ||
|
||
For the `TodoList`, we have to do a bit more work. The `todos` field is not directly supported by `json_serializable`. It has no understanding of the `ObservableList<Todo>` type. We have to help it out with a type-helper, essentially a `JsonConverter<T, S>` class. | ||
|
||
```dart | ||
@JsonSerializable() | ||
class TodoList = _TodoList with _$TodoList; | ||
abstract class _TodoList with Store { | ||
@observable | ||
@_ObservableListConverter() | ||
ObservableList<Todo> todos = ObservableList<Todo>(); | ||
// ... | ||
} | ||
``` | ||
|
||
Notice the use of the `@_ObservableListConverter()` annotation. This is the custom type-helper (`JsonConverter<T, S>`) that will handle the serialization for the `ObservableList<Todo>`. This converter class is as below: | ||
|
||
```dart | ||
class _ObservableListConverter | ||
implements JsonConverter<ObservableList<Todo>, List<Map<String, dynamic>>> { | ||
const _ObservableListConverter(); | ||
@override | ||
ObservableList<Todo> fromJson(List<Map<String, dynamic>> json) => | ||
ObservableList.of(json.map(Todo.fromJson)); | ||
@override | ||
List<Map<String, dynamic>> toJson(ObservableList<Todo> list) => | ||
list.map(Todo.toJson).toList(); | ||
} | ||
``` | ||
|
||
> A similar kind of converter would be needed for `ObservableSet<T>` and `ObservableMap<T>` for fields that have those types. | ||
## On with the code-generation | ||
|
||
With these changes, let's run the `build_runner` command in the project folder: | ||
|
||
``` | ||
flutter packages pub run build_runner watch --delete-conflicting-outputs | ||
``` | ||
|
||
This generates the code the looks like so for the `Todo` class, found in `todo.g.dart`. | ||
|
||
```dart | ||
// GENERATED CODE - DO NOT MODIFY BY HAND | ||
part of 'todo.dart'; | ||
// ************************************************************************** | ||
// JsonSerializableGenerator | ||
// ************************************************************************** | ||
Todo _$TodoFromJson(Map<String, dynamic> json) { | ||
return Todo(json['description'] as String)..done = json['done'] as bool; | ||
} | ||
Map<String, dynamic> _$TodoToJson(Todo instance) => <String, dynamic>{ | ||
'description': instance.description, | ||
'done': instance.done | ||
}; | ||
``` | ||
|
||
> We have wrapped these methods in the `Todo` class with the convenience functions `toJson` and `fromJson`. | ||
The `TodoList` serialization is a bit more involved but should not be too hard to follow. Note that it also includes the serialization for the Enum `VisibilityFilter`. This is in the `todo_list.g.dart`. | ||
|
||
```dart | ||
// GENERATED CODE - DO NOT MODIFY BY HAND | ||
part of 'todo_list.dart'; | ||
// ************************************************************************** | ||
// JsonSerializableGenerator | ||
// ************************************************************************** | ||
TodoList _$TodoListFromJson(Map<String, dynamic> json) { | ||
return TodoList() | ||
..todos = const _ObservableListConverter() | ||
.fromJson(json['todos'] as List<Map<String, dynamic>>) | ||
..filter = _$enumDecodeNullable(_$VisibilityFilterEnumMap, json['filter']) | ||
..currentDescription = json['currentDescription'] as String; | ||
} | ||
Map<String, dynamic> _$TodoListToJson(TodoList instance) => <String, dynamic>{ | ||
'todos': const _ObservableListConverter().toJson(instance.todos), | ||
'filter': _$VisibilityFilterEnumMap[instance.filter], | ||
'currentDescription': instance.currentDescription | ||
}; | ||
T _$enumDecode<T>(Map<T, dynamic> enumValues, dynamic source) { | ||
if (source == null) { | ||
throw ArgumentError('A value must be provided. Supported values: ' | ||
'${enumValues.values.join(', ')}'); | ||
} | ||
return enumValues.entries | ||
.singleWhere((e) => e.value == source, | ||
orElse: () => throw ArgumentError( | ||
'`$source` is not one of the supported values: ' | ||
'${enumValues.values.join(', ')}')) | ||
.key; | ||
} | ||
T _$enumDecodeNullable<T>(Map<T, dynamic> enumValues, dynamic source) { | ||
if (source == null) { | ||
return null; | ||
} | ||
return _$enumDecode<T>(enumValues, source); | ||
} | ||
const _$VisibilityFilterEnumMap = <VisibilityFilter, dynamic>{ | ||
VisibilityFilter.all: 'all', | ||
VisibilityFilter.pending: 'pending', | ||
VisibilityFilter.completed: 'completed' | ||
}; | ||
``` | ||
|
||
## 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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters