Reputation: 11
I am currently using json_serializable
with generic objects in Dart, where the generics should freezed classes (have toJson
methos and fromJson
constructor). I have a working implementation, but I'm wondering if there's a more efficient or cleaner way to achieve this.
I have a package with some models that accept generics, so I want to export it but need an easy way to people set the behavior of the serialization in a easy way, The ideal solution is that they didn't have to do anything.
In my current approach, I assume that every object has a toJson
method. I also maintain a global mutable variable fromJsonFactories
, where the corresponding fromJson
is assigned for each type. I've seen generic_argument_factories
, but it seems to add a lot of boilerplate for each implementation.
Here's a simplified version of my current implementation:
// Override this map with the factories for the types you want to deserialize.
// Example:
// fromJsonFactories = {
// ModelA: (json) => ModelA.fromJson(json),
// };
Map<Type, Object Function(Map<String, dynamic> json)> fromJsonFactories = {};
class GenericJsonConverter<T> implements JsonConverter<T, Object?> {
const GenericJsonConverter();
@override
T fromJson(Object? json) {
return fromJsonFactories[T]!(json as Map<String, dynamic>) as T;
}
@override
Object? toJson(T object) {
return (object as dynamic).toJson();
}
}
For now whoever uses the models from my package/library has to override fromJsonFactories
in the main.
The idea would be that they didn't have to do anything.
I initially tried to avoid using a global variable, as I know all classes will have a factory fromJson
. However, this approach resulted in an error:
@override
T fromJson(Object? json) {
// Error: Class 'Type' has no instance method 'fromJson'.
return (T as dynamic).fromJson() as T;
}
You can find the full example of my current implementation in this gist.
Any suggestions for improving this implementation would be greatly appreciated.
Thanks in advance!
Upvotes: 0
Views: 152
Reputation: 1262
Unfortunately, Dart does not support dispatching a function call based on a Type
and for your solution to work you need a mapping from Type
to fromJsonFactory
.
To make it easier for users of your package I would suggest storing the fromJson factories as a static variable and defining a static function to register types with GenericJsonConverter
.
import 'package:freezed_annotation/freezed_annotation.dart';
part 'generic_json_serializable_converter_test.freezed.dart';
part 'generic_json_serializable_converter_test.g.dart';
typedef FromJsonFactory<T> = T Function(Map<String, dynamic> json);
class GenericJsonConverter<T> implements JsonConverter<T, Object?> {
const GenericJsonConverter();
static final Map<Type, FromJsonFactory> _fromJsonFactories = {};
@override
T fromJson(Object? json) {
return _fromJsonFactories[T]!(json as Map<String, dynamic>) as T;
}
/// Registers a FromJsonFactory with return type `T`.
static register<T>(FromJsonFactory<T> fromJsonFactory) {
_fromJsonFactories[T] = fromJsonFactory;
}
@override
Object? toJson(T object) {
return (object as dynamic).toJson();
}
}
To register a FromJsonFactory
one would then use:
void main() {
GenericJsonConverter.register<ModelA>((json) => ModelA.fromJson(json));
}
Upvotes: 0