Consider the following example:
import 'package:freezed_annotation/freezed_annotation.dart';
part 'main.freezed.dart';
part 'main.g.dart';
@freezed
abstract class TodoList with _$TodoList {
factory TodoList(List<Todo> todos) = _TodoList;
factory TodoList.fromJson(Map<String, dynamic> json) => _$TodoListFromJson(json);
}
@freezed
abstract class Todo with _$Todo {
factory Todo(String name, bool checked) = _Todo;
factory Todo.fromJson(Map<String, dynamic> json) => _$TodoFromJson(json);
}
Observe the following test fail:
import 'package:flutter_test/flutter_test.dart';
import 'package:freezed_json_bug/freezed_json_bug.dart';
import 'package:collection/collection.dart';
void main() {
test('serialize deserialize', () {
final todos = <Todo>[
Todo("Item 1", true),
Todo("Item 2", false),
];
final todoList = TodoList(todos);
final todoListJson = todoList.toJson();
final todoListReserialized = TodoList.fromJson(todoListJson);
Function deepEq = const DeepCollectionEquality().equals;
expect(deepEq(todoList.toJson(), todoListReserialized.toJson()), true);
});
}
with the following stacktrace:
type '_$_Todo' is not a subtype of type 'Map<String, dynamic>' in type cast
package:freezed_json_bug/src/main.g.dart 13:55 _$_$_TodoListFromJson.<fn>
dart:_internal ListIterable.toList
package:freezed_json_bug/src/main.g.dart 14:11 _$_$_TodoListFromJson
package:freezed_json_bug/src/main.freezed.dart 89:7 new _$_TodoList.fromJson
package:freezed_json_bug/src/main.freezed.dart 12:20 _$TodoListFromJson
package:freezed_json_bug/src/main.dart 9:59 new TodoList.fromJson
test/freezed_json_bug_test.dart 17:43 main.<fn>
This is because in main.g.dart we have:
Map<String, dynamic> _$_$_TodoListToJson(_$_TodoList instance) =>
<String, dynamic>{
'todos': instance.todos,
};
rather than:
Map<String, dynamic> _$_$_TodoListToJson(_$_TodoList instance) =>
<String, dynamic>{
'todos': instance.todos.map((e) => e.toJson()).toList(),
};
This is the "expected" behavior of json_serializable
You are supposed to either specify a @JsonSerializable(explicitToJson: true) or change explicitToJson inside your build.yaml file. https://github.com/google/json_serializable.dart/tree/master/json_serializable#build-configuration
Thanks for the quick explainer, adding that annotation fixed the test:
@freezed
abstract class Todo with _$Todo {
// must be above factory method
@JsonSerializable(explicitToJson: true)
factory Todo(String name, bool checked) = _Todo;
factory Todo.fromJson(Map<String, dynamic> json) => _$TodoFromJson(json);
}
Optionally creating build.yaml mentioned in https://github.com/rrousselGit/freezed/issues/86 (only stumbled upon this now 馃槄 ) is a fix too:
targets:
$default:
builders:
json_serializable:
options:
explicit_to_json: true
Out of these two I think of going for build.yaml fix and making this explicitToJson true by default everywhere. It's less code to write and less things to worry about.
Most helpful comment
Thanks for the quick explainer, adding that annotation fixed the test:
Optionally creating build.yaml mentioned in https://github.com/rrousselGit/freezed/issues/86 (only stumbled upon this now 馃槄 ) is a fix too:
Out of these two I think of going for
build.yamlfix and making thisexplicitToJsontrue by default everywhere. It's less code to write and less things to worry about.