Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why an explicit ".cast<>()" function in Dart instead of "as <>"

Tags:

dart

In my question Dart 2.X List.cast() does not compose the answer requires converting a List<dynamic> to a List<String> as such:

List<String> ls = (json['data'] as List).cast<String>().map((s) => s.toUpperCase()).toList();

My experience from other languages had me write this first:

List<String> ls = (json['data'] as List<String>).map((s) => s.toUpperCase()).toList();

Note that this compiles but fails at runtime in Dart 2.

Why does Dart typecasting for a List require a function as List).cast<String>() as opposed to simply using the Dart as "typecast operator" such as as List<String>?

---- Edit ----

I am using the most recent Dart 2.0.0-dev.43.0 and get inconsistent runtime behavior with as typecasts/assertions. Isn't the .cast<>() function creating a new iterable the same as a .map()? Changing my code to this works:

List<String> ls = (json['data'] as List).map((s) => (s as String).toUpperCase()).toList();

This seems to take advantage that the first cast to List is a List<dynamic>. Thus the .map function parameter is also a dynamic.

My second example above with to as List<String> works in some places in our code but not others. Note that IntelliJ correctly infers the types in all of the above examples - it's the runtime where the failure happens. I'm guessing that the inconsistent behavior is due to Dart 2.x being still in development.

---- 2nd Edit ----

Here are my test cases that I have in one of my class constructors:

Map<String, dynamic> json = { "data": ["a", "b", "c"] };

//List<String> origBroken = json["data"].map( (s) => s.toUpperCase() ).toList();

// Sometimes works - sometimes gives "Ignoring cast fail from JSArray to List<String>" at runtime!!!
List<String> wonky = (json["data"] as List<String>).map( (s) => s.toUpperCase() ).toList();
print("Wonky $wonky");

List<String> fix1 = (json["data"] as List).cast<String>().map( (s) => s.toUpperCase() ).toList();
List<String> fix2 = (json["data"] as List).map( (s) => (s as String).toUpperCase() ).toList();
List<String> explicit2 = (json["data"] as List<dynamic>).map( (dynamic s) => (s as String).toUpperCase() ).toList();

// From accepted answer of the linked question - compile error because .cast() doesn't take parameters
//   error: Too many positional arguments: 0 expected, but 1 found.
//List<String> notBroken = (json['data'] as List).cast<String>((s) => s.toUpperCase()).toList();
List<String> notBrokenFixed = (json['data'] as List<String>).cast<String>().map((String s) => s.toUpperCase()).toList();

The problem is the warning Ignoring cast fail from JSArray to List<String> sometimes given by the wonky assignment. When I say sometimes it's because it changes unpredictably as I make changes to the main application that uses the library that contains this code - without making changes to this class or even the library.

At the time I wrote the first edit above, wonky wasn't working. I just tried it again now and it's working. I have not changed any code in this library - I have been working in the main application which has a dependency on this code's library.

Some background, this is a multi-library project being converted from Angular/Typescript. These test cases are based on the processing we do to deserialize JSON into Dart classes. We map JSON (dynamic) strings into various data structures such as enums, Option<> and Either<> (from dartz) using class constructor initializers.

A couple weeks ago the runtime warning started happening I believe because of Breaking Change: --preview-dart-2 turned on by default. I understand that this warning will soon be an error. So I traced the warning back to these conversions that map from JSON dynamic data (Yes, dynamic data is an edge case in Dart but it's what dart:convert provides).

We are developing on Mac using DDC with the most recent Dart 2.0.0-dev.43.0, angular 5.0.0-alpha+8, build_runner 0.8.0, IntelliJ 2018.1 and running on Chrome 65.0.3325.181.

---- Final Edit ----

There is an instability in the current development build/runtime that is behind this issue. No, I don't have a reproducible example. Changing and rebuilding our main app will cause this code in an unmodified library dependency to sometimes give the runtime warning Ignoring cast fail from JSArray to List<String>.

The suspect code from the original part of this question (also wonky above)

List<String> ls = (json['data'] as List<String>).map((s) => s.toUpperCase()).toList();

casts the dynamic JSON data to a List<String>. The types are fully constrained and the Dart analyzer/IntelliJ infers s to be Static type: String.

The runtime warning that sometimes occurs and related answers to use .cast() is what led to this question. At this time I'll believe the analyzer and ignore the runtime warning.

like image 643
Richard Johnson Avatar asked Mar 28 '18 18:03

Richard Johnson


1 Answers

In Dart 2 generic types are reified.

as ... is more like an assertion, if the values type doesn't match as causes a runtime exception.

cast<T>() is a method introduced in Dart 2 on Iterable that actually creates a new iterable of type Iterable<T> (or in your case the subclass List<T>) filled with the values of the original interable.


Update

You can use print('wonky: ${wonky.runtimeType}'); to see what the actual type is.

If the type matches your requirement, you can use as to communicate it to the analyzer that it's safe to assume this type.

If the type doesn't match, for example because it is List instead of List<String>, then you can use .cast<String>() to actually make it a List<String>.

List<String> broken = (json['data'] as List)
  .cast<String>((s) => s.toUpperCase()).toList();

Here you seem to try to use cast for casting and mapping, but that is not valid. map() can do both though

List<String> notBroken = (json['data'] as List)
  .map<String>((s) => s.toUpperCase()).toList();
like image 73
Günter Zöchbauer Avatar answered Nov 15 '22 21:11

Günter Zöchbauer