Difference between casting with 'cast' method and 'as' keyword

Viewed 286

What's the difference in Dart between casting with the as keyword and casting with the cast method?

See the following example:

import 'dart:convert';

class MyClass {
  final int i;
  final String s;

  MyClass({required this.i, required this.s});

  factory MyClass.fromJson(Map<String, dynamic> json) =>
      MyClass(s: json["s"], i: json["i"]);
}

void main() {
  const jsonString = '{"items": [{"i": 0, "s": "foo"}, {"i": 1, "s":"bar"}]}';
  final json = jsonDecode(jsonString);

  final List<MyClass> thisWorks = (json["items"] as List)
      .cast<Map<String, dynamic>>()
      .map(MyClass.fromJson)
      .toList();

  final List<MyClass> thisAlsoWorks = (json["items"] as List)
      .map((json) => MyClass.fromJson(json as Map<String, dynamic>))
      .toList();

  final List<MyClass> thisCrashes =
      (json['items'] as List<Map<String, dynamic>>)
          .map(MyClass.fromJson)
          .toList();
}

The last call (casting with as) results in an exception: type 'List<dynamic>' is not a subtype of type 'List<Map<String, dynamic>>' in type cast.

I expected that casting with as would work like casting with the cast method without resulting in an exception.

1 Answers

as performs a cast that, after performing a runtime check, changes the static type of an object. It does not affect the identity of the object.

Collections (e.g. List, Map, Set) provide a .cast method that returns a new object (a "view") of the collection that performs as casts for each element.

void main() {
  // `list1` is created as a `List<Object>` but happens to store only ints.
  List<Object> list1 = <Object>[1, 2, 3];

  try {
    // This cast will fail because `list1` is not actually a `List<int>`.
    list1 as List<int>;
  } on TypeError catch (e) {
    print(e); // This gets printed.
  }

  // `list2` is a `List`-like object that downcasts each element of `list1`
  // to an `int`. `list2` is of type `List<int>`.
  //
  // Note that `list2` is not a copy of `list1`; mutations to `list2` will
  // affect `list1`.
  List<int> list2 = list1.cast<int>();
  
  // `list3` is `list2` upcast to a `List<Object>`.  If a class `Derived`
  // derives from a class `Base`, then Dart also considers `List<Derived>`
  // to be a derived class of `List<Base>`, so no explicit cast is necessary.
  List<Object> list3 = list2;
  
  // `list4` is `list3` downcast to `List<int>`.  Since `list3` is the
  // same object as `list2`, and since `list2`'s actual runtime type is
  // `List<int>`, this cast succeeds (unlike `list1 as List<int>`).
  List<int> list4 = list3 as List<int>;

  print(identical(list1, list2)); // Prints: false
  print(identical(list2, list3)); // Prints: true
  print(identical(list3, list4)); // Prints: true
}
Related