Brandon Tyler Diamond
Brandon Tyler Diamond

Reputation: 35

How to go from a runtime type (Type) to a bound type parameter (T)?

tl;dr -- I'm deserializing objects (e.g., an "Animal" instance) without reflection and want to obtain its actual type so that I can construct related objects (e.g., "Result<Animal>"). I'm also writing framework code so can't simply cast to a type explicitly. Is there a reasonable way to do this?

I'm dealing with serialization and want to be able to use the resulting type in constructing related objects: e.g., if I'm deserializing into "Animal", I want to instantiate a container, e.g., "Result<Animal>". In order to do that, I need a type (T) rather than a type instance (Type).

I've found that I can create a list of statically typed helper classes beforehand (Helper<Animal>, Helper<Person>, ...), find the right one via "deserializedAnimal.runtimeType", then invoke, say, "buildResult(deserializedAnimal)". This, being specifically instantiated for Animals, knows that it's dealing with an Animal and can explicitly create a Result<Animal>.

I've also found a way to do this generically by adding a method to the helper with a generic closure argument. The helper binds the closure's type parameter to the static type before invoking it. As a result, the closure receives an instance that has a matching runtime type and static type -- plus its type parameter is correctly bound and can be used to instantiate other types (e.g., Result<T> with T bound to Animal).

All of this feels a bit convoluted but I'm convinced I need something like this to restore the type parameter after serialization (again, so I can pass the result to a generic and trust that the type parameter is bound to the actual static type rather than, say, dynamic).

Does any of this seem wonky? Am I missing something easier?

Example code:

class Identifiable<T> {
  Id<T> id;
}

class Id<T> {}

class Person extends Identifiable<Person> {}

class Animal extends Identifiable<Animal> {}

class Resolver<T extends Identifiable> {
  void resolve(dynamic arg, void Function<V extends Identifiable>(dynamic) func) {
    func<T>(arg);
  }
}

final dynamic resolvers = {
  Id<Person>().runtimeType: Resolver<Person>(),
  Id<Animal>().runtimeType: Resolver<Animal>()
};

void reveal<T extends Identifiable>(Id<T> id) {
  print("Static: ${Id<T>().runtimeType}");
  print("Dynamic: ${id.runtimeType}\n");
}

void resolve(dynamic arg, void Function<V extends Identifiable>(dynamic) func) {
  resolvers[arg.runtimeType].resolve(arg, func);
}

main() {
  dynamic lst = [];
  
  lst.add(Id<Person>());
  lst.add(Id<Animal>());
  
  /*
    Static: Id<Identifiable<dynamic>>
    Dynamic: Id<Person>
  */
  reveal(lst[0]);

  /*
    Static: Id<Person>
    Dynamic: Id<Person>
  */
  resolve(lst[0], <V extends Identifiable>(arg){
    reveal<V>(arg);
  });
}

Upvotes: 1

Views: 430

Answers (1)

lrn
lrn

Reputation: 71623

What you are doing is the simplest way to go from Type object to type variable bound to that type (or any other functionality which depends on the precise type, like a factory function).

There is no way to go directly from Type object to a usable type without reflection, because doing that is reflection.

Upvotes: 2

Related Questions