profile
viewpoint

Ask questionsImproved support for runtimeType and <T> type params

I would like to be able to use runtimeType and/or be able to interrogate the type of a <T> type param at runtime, to improve deserialization of Lists in a generic way.

Use case

I have a json list, containing SearchResult items. I have a <T> type param representing the desired object List<SearchResult>. I also have an empty List<SearchResult> object that I could deserialize into:

T deserialize<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<SearchResult>" 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<SearchResult>", 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:

  1. Slightly improved dart:mirrors support in Flutter, eg a cut-down version of ClassMirror would probably suffice, or
  2. a documented API on Type to return the name of the type, or
  3. 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:
void check<T>(T object) {
//We can test object is List:
  print(object is List); // true
  print(object is T); // true
//But no way to test if T is a list:
  print(T is List); // false
  print(T == List); // false
}
void main() => 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.

Complete example

class SearchRequest implements IReturn<List<SearchResult>>{
    String searchTerm;
    List<SearchResult> createResponse() {
      return new List<SearchResult>();
    }
}
class SearchResult implements IConvertible {
  String name;
  String parent;

  SearchResult({this.name, this.parent});
  SearchResult.fromJson(Map<String, dynamic> json) {
    fromMap(json);
  }

  fromMap(Map<String, dynamic> json) {
    name = json['name'];
    parent = json['parent'];
    return this;
  }

  Map<String, dynamic> toJson() => {
        'name': name,
        'parent': parent,
      };

  TypeContext context = _typeFactory;
}

abstract class IConvertible {
  TypeContext context;
  fromMap(Map<String, dynamic> map);
  Map<String, dynamic> toJson();
}
abstract class IReturn<T> {
  T createResponse();
}

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:

T deserialize(IReturn<T> request, dynamic jsonResponse){
T responseType = request.createResponse();
if(responseType is List){
var jsonList = jsonResponse as List<dynamic>;
//TODO: somehow extract the element type E from T==List<E> -> "SearchResult" 
var elementTypeStr = T.toString().replaceFirst("List<","").replaceFirst(">",""); //undocumented option 2
return jsonList.map((e)=>_typeFactory[elementTypeStr].create().fromMap(e));
}
//else standard object response
return _typeFactory(responseType).create().fromMap(jsonResponse);
}
flutter/flutter

Answer questions VladyslavBondarenko

Hi @xvrh please file the issue at https://github.com/dart-lang/sdk/issues (or even better https://github.com/dart-lang/language/issues), since this is rather about dart, not flutter, closing. If you disagree please write in the comments and I will reopen it

useful!

Related questions

android license status unknown hot 21
Unable to run &#39;adb&#39; check your Android SDK hot 12
Android studio stuck when creating new flutter project hot 9
Am trying to use SVG as decoration background, how do I do it? hot 8
Can't input text in TextField. hot 8
type 'List<dynamic>' is not a subtype of type 'List<String>' hot 8
flutter doctor is not working, Error : Checking Dart SDK version... &#39;PowerShell.exe&#39; is not recognized as an internal or external command, operable program or batch file. Error: Unable to update Dart SDK. Retrying... hot 7
Setting VM flags failed: Unrecognized flags: checked hot 7
The method &#39;ancestorStateOfType&#39; was called on null hot 6
TextField is hidden by keyboard inside of a Modal Bottom Sheet hot 6
Error: Error when reading 'bin/main.dart': The system cannot find the path specified. hot 6
google sign in ^4.0.1+3 plugin: PlatformException(sign_in_failed, com.google.android.gms.common.api.ApiException: 10: , null) hot 6
A non-null String must be provided to a Text widget. hot 6
[google_maps_flutter] Trying to create an already created platform view hot 6
Flutter ImagePicker Plugin (No implementation found for method pickImage)
Github User Rank List