You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I would like to be able to use runtimeType and/or be able to interrogate the type of a type param at runtime, to improve deserialization of Lists in a generic way.
Use case
In Flutter, I have a json list, containing SearchResult items. I have a type param representing the desired object List. I also have an empty List<SearchResult> object that I could deserialize into:
Tdeserialize<T>(IReturn<T> request, dynamic jsonResponse){
T responseType = request.createResponse(); //creates an empty object of the desired type, in this case List<SearchResult>
...
}
The issue I am having is I can't find a reliable way to obtain "SearchResult" inside this function. (I then look up "SearchResult" in a type factory dictionary, to obtain a function that deserializes each json list element to concrete SearchResult instances).
Unreliable option 1:
responseType.runtimeType.toString().replaceFirst("List<","").replaceFirst(">","");
This worked fine in debug mode, giving "SearchResult", but when I went to release mode some time later, it failed. This was because List<SearchResult>().runtimeType.toString() gives "_GrowableList" in release mode.
This specific behaviour isn't documented anywhere, there's only a warning not to use runtimeType for anything in Chapter 2, section 2.13 of "The Dart Programming Language" by Gilad Bracha:
The only way to reliably discover what the class of an object is is via reflection, which we will discuss extensively in Chapter 7. Objects do support a getter runtimeType, which by default does return the class of the object. However, subclasses are free to override runtimeType
and then a bit after that:
More generally, the principle that the only thing that matters about an object is its behavior can all too easily be violated. It requires great care to ensure that nothing in the language causes irrelevant implementation details of this nature to leak.
So basically I gather you can't and really shouldnt rely on runtimeType.
Unreliable option 2:
T.toString().replaceFirst("List<","").replaceFirst(">","");
This works, because T.toString() gives "List", on both dev and release builds (no _GrowableList appears). But this is pretty unreliable too, as it's just a .toString() which is undocumented. There are no other methods available on T to call (T.runtimeType.toString() gives "_Type").
Proposal
Either:
Slightly improved dart:mirrors support in Flutter, eg a cut-down version of ClassMirror would probably suffice, or
a documented API on Type to return the name of the type, or
Improved functionality for <T> type tokens, either by making T.toString() a documented API, or providing an alternative API that returns the name of the type represented by the token. It's great that there's no type erasure (unlike Java), but its usefulness is still limited. You can't test if T is a list:
voidcheck<T>(T object) {
//We can test object is List:print(object isList); // trueprint(object isT); // true//But no way to test if T is a list:print(TisList); // falseprint(T==List); // false
}
voidmain() =>check(['a', 'b']);
Extra background
I'm trying to fix an issue with the ServiceStack dart client, which has issues deserializing List API responses. ServiceStack codegen gives us a nicer way to generate strongly-typed APIs in .NET, and call them from many many client languages.
Request classes implement IReturn<T> where T is the return type.
Usage: Given a ServiceStack client with a generic send function: Future<T> send<T>(IReturn<T> request),
and a request of class: class SearchRequest implements IReturn<List<SearchResult>>
Then in dart we can write: var result = await client.send(SearchRequest(searchTerm: query));
and result is inferred to be of type List<SearchResult>
It makes API calls extremely easy.
The IReturn request objects implement a function T createResponse(), which creates a new, empty response object, on which fromMap(Map<String, dynamic> json) is then called to inflate it with the json response. This works fine for Object responses, but not for Lists.
we then look up the type, "SearchResult", inside a type factory dictionary, which looks something like: _typeFactory = <String, IConvertible Function()>{'SearchResult': () => new SearchResult(),...}
So then the deserializer can call:
Tdeserialize(IReturn<T> request, dynamic jsonResponse){
T responseType = request.createResponse();
if(responseType isList){
var jsonList = jsonResponse asList<dynamic>;
//TODO: somehow extract the element type E from T==List<E> -> "SearchResult" var elementTypeStr =T.toString().replaceFirst("List<","").replaceFirst(">",""); //undocumented option 2return jsonList.map((e)=>_typeFactory[elementTypeStr].create().fromMap(e));
}
//else standard object responsereturn_typeFactory(responseType).create().fromMap(jsonResponse);
}
The text was updated successfully, but these errors were encountered:
I think this request is covered here, with a specific proposal addressing it here. This comes up from time to time, and is probably worth us doing something about, but we don't have any short term plans to tackle this.
Note that if you can control the classes that want to introspect on, you can do this using generic methods as described here. But if you don't control the classes in question (e.g. native List types) then you're out of luck.
I'm going to close this out, since I think it's well-tracked by the issues above (feel free to add your example there). If you feel that the above issues don't capture your request, feel free to re-open.
IMO this doesn't resolve the issue where the behavior is different depending on the platform it's running on, i.e.
List<SearchResult>().runtimeType.toString();
Resolves to List<SearchResult> in Dart console app, Flutter tests, Flutter App running in an emulator, etc. but not on device.
What we need is a consistent way to resolve the instantiated type i.e. List<SearchResult>() available as a string (which we use as a map of factor constructors). Returning an internal _GrowableList Type on device we can't instantiate isn't useful & given the limited introspection capabilities available in Dart, the APIs that do exist should at least be consistent as this is resulting in runtime errors on device that we can't test for.
I would like to be able to use
runtimeType
and/or be able to interrogate the type of a type param at runtime, to improve deserialization of Lists in a generic way.Use case
In Flutter, I have a json list, containing
SearchResult
items. I have a type param representing the desired object List. I also have an emptyList<SearchResult>
object that I could deserialize into:The issue I am having is I can't find a reliable way to obtain "SearchResult" inside this function. (I then look up "SearchResult" in a type factory dictionary, to obtain a function that deserializes each json list element to concrete SearchResult instances).
Unreliable option 1:
responseType.runtimeType.toString().replaceFirst("List<","").replaceFirst(">","");
This worked fine in debug mode, giving "SearchResult", but when I went to release mode some time later, it failed. This was because
List<SearchResult>().runtimeType.toString()
gives "_GrowableList" in release mode.This specific behaviour isn't documented anywhere, there's only a warning not to use
runtimeType
for anything in Chapter 2, section 2.13 of "The Dart Programming Language" by Gilad Bracha:and then a bit after that:
So basically I gather you can't and really shouldnt rely on
runtimeType
.Unreliable option 2:
T.toString().replaceFirst("List<","").replaceFirst(">","");
This works, because T.toString() gives "List", on both dev and release builds (no _GrowableList appears). But this is pretty unreliable too, as it's just a
.toString()
which is undocumented. There are no other methods available on T to call (T.runtimeType.toString()
gives "_Type").Proposal
Either:
dart:mirrors
support in Flutter, eg a cut-down version ofClassMirror
would probably suffice, orType
to return the name of the type, or<T>
type tokens, either by makingT.toString()
a documented API, or providing an alternative API that returns the name of the type represented by the token. It's great that there's no type erasure (unlike Java), but its usefulness is still limited. You can't test if T is a list:Extra background
I'm trying to fix an issue with the ServiceStack dart client, which has issues deserializing List API responses. ServiceStack codegen gives us a nicer way to generate strongly-typed APIs in .NET, and call them from many many client languages.
Request classes implement
IReturn<T>
whereT
is the return type.Usage: Given a ServiceStack
client
with a genericsend
function:Future<T> send<T>(IReturn<T> request)
,and a request of class:
class SearchRequest implements IReturn<List<SearchResult>>
Then in dart we can write:
var result = await client.send(SearchRequest(searchTerm: query));
and result is inferred to be of type
List<SearchResult>
It makes API calls extremely easy.
The
IReturn
request objects implement a functionT createResponse()
, which creates a new, empty response object, on whichfromMap(Map<String, dynamic> json)
is then called to inflate it with the json response. This works fine for Object responses, but not for Lists.Complete example
we then look up the type, "SearchResult", inside a type factory dictionary, which looks something like:
_typeFactory = <String, IConvertible Function()>{'SearchResult': () => new SearchResult(),...}
So then the deserializer can call:
The text was updated successfully, but these errors were encountered: