Generic constraints in Dart

Viewed 255

I need to abstract my model parsing system in a http request

I have an HttpClient class

class HttpClient {
   Dio dio = new Dio();
   
   Future<T> get<T extends BaseModel>(String url) {
      Response response = await dio.get(url);
      T.fromJson(response.body);
   }
}

my BaseModel class is this:

abstract class BaseModel {
 fromJson(Map<String, dynamic> json);
 toJson();
}

and an example of a model:

class Person extends BaseModel {
  String name;
  String surname;
  int age;

  Person({this.name, this.surname, this.age});

  Person.fromJson(Map<String, dynamic> json) {
    // parse ToModel;
  }

  Map<String, dynamic> toJson() {
    // parse toJson;
  }
}

and i want to consume my HttpClient like this:

class RandomClassToConsumeHttp {
   
   HttpClient _httpClient;
   Person person;  

   RandomClassToConsumeHttp(this._httpClient);


   void _getPerson() async {
     _person = await _httpClient.get<Person>("api/getPerson");
   }
}

the problem is:

I have a generic in my get method and i need the generic to extends the BaseModel class, so i always will have a implementation of fromJson(), and do the model parse based on the model type i sent to the generic

i dont know if is possible to do some generic constraints like this in dart, and i need to use fromJson from that type, anyone knows if its possible ? or if there is another solution

   Future<T> get<T extends BaseModel>(String url) {
      Response response = await dio.get(url);
      T.fromJson(response.body);
   }
1 Answers

Unfortunately, T.fromJson() is not possible. The workaround is to maintain factories to create instances.

Some caveats are:

  1. You can't tearoff named constructors.
  2. Static functions are not inherited and there is no way to require subclasses to implement them.
  3. Having a non-static virtual from or create function complicates immutable classes and non-nullable-by-default code.
  4. Every applicable subclass needs to both implement the create or from pattern and be added to the factory map.

All in all, I think it would be better to pass the json received to a model's constructor like:

var model = SomeModel.fromJson(httpClient.get('resource'));

Lastly, here is a factory implementation example:

abstract class Base {
  static from(v) => SubA.from(v);
}

class SubA extends Base {
  static from(v) => SubA();
}

class SubB extends Base {
  static from(v) => SubB();
}

final _baseFromFactory = {
  Base: Base.from,
  SubA: SubA.from,
  SubB: SubB.from,
  // or you can do:
  // SubB: (v) => /* create instance */
};

class Client {
  get<T extends Base>(v) => _baseFromFactory[T](v);
  
  // Alternatively, you can switch on T.
  getAlt<T extends Base>(v) {
    switch (T) {
      case SubB: return SubB.from(v);
      default: return SubA.from(v);
    }
  }
}

main() {
  var client = Client();
  var subA = client.get<SubA>('a value');
  var subB = client.get<SubB>('a value');
  var altA = client.getAlt<SubA>('a value');
  var altB = client.getAlt<SubB>('a value');

  print(subA);
  print(subB);
  print(altA);
  print(altB);
}
Related