Reputation: 35
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 Animal
s, 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
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