MiniSuperDev
MiniSuperDev

Reputation: 11

Seeking Efficient Use of json_serializable with Generics in Dart for export models in a package/library

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

Answers (1)

Dan R
Dan R

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

Related Questions