Skip to content

Commit

Permalink
Adding a detailed guide about json serialization.
Browse files Browse the repository at this point in the history
  • Loading branch information
pavanpodila committed Jul 13, 2019
1 parent 9f2391c commit 749a996
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 8 deletions.
14 changes: 7 additions & 7 deletions .idea/workspace.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

168 changes: 168 additions & 0 deletions docs/src/guides/json-serialization.mdx
@@ -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.
2 changes: 1 addition & 1 deletion mobx_examples/lib/todos/todo.dart
Expand Up @@ -3,7 +3,7 @@ import 'package:mobx/mobx.dart';

part 'todo.g.dart';

@JsonSerializable(nullable: false)
@JsonSerializable()
class Todo extends _Todo with _$Todo {
Todo(String description) : super(description);

Expand Down

0 comments on commit 749a996

Please sign in to comment.