0

I need to cast a List and a Map of unknown types to specific types. If the List or Map are not empty all is well. But if they are ever empty I get a cast error.

When list is empty:

final list = [];
final result = list as List<Map<String, dynamic>?>;

Output

type 'List<dynamic>' is not a subtype of type 'List<Map<String, dynamic>?>' in type cast

When Map is empty:

final map = {};
final result = map as Map<String, dynamic>;

Output

type '_Map<dynamic, dynamic>' is not a subtype of type 'Map<String, dynamic>' in type cast

For context: I'm fetching data from firebase. The DocumentSnaphot.data() of the firebase package returns an Object?. Therefore I have no control over what type the data is. Hence the casting...

JakesMD
  • 1,646
  • 2
  • 15
  • 35
  • Have you tried checking whether it is empty and do the casting only if it is not empty, otherwise assign a specific value? – Peter Koltai Aug 23 '23 at 19:52
  • Yes that would work if I had just a List or just a Map. But here they're nested. What if the list only contained multiple empty Maps? I would have to loop through the list to check and cast each individual map. Seems rather unnecessary... – JakesMD Aug 23 '23 at 20:48
  • "If the List or Map are not empty all is well." That seems very unlikely. You cannot cast `List`s or `Map`s that way with `as`; `as` does not perform any runtime transformation on the object and would fail for non-empty collections too. For example, `['foo'] as `List` will also fail. If you want to cast the *elements* of a `List`, then you should use `List.cast`/`Map.cast`. Also see https://stackoverflow.com/a/70963189/ – jamesdlin Aug 24 '23 at 02:53
  • Or if you just want your empty `List`s and `Map`s to have the correct type in the first place, you can do: `final list = ?>[{}];` – jamesdlin Aug 24 '23 at 02:55

2 Answers2

0

You can use pattern matching to respond to the different types returned by Firebase.

Here's an example:

void parseData(Object? data) {
  switch (data) {
    /// Handle empty list.
    case []:
      log("Received empty list.");
    /// Matches a list of integers.
    case List<int> list:
      log("Received integer list: $list");
    /// Matches when it's a list of maps whose key is of type String, but value
    /// can be any type (dynamic).
    case List<Map<String, dynamic>> list:
      log("Received list of maps: $list");
    /// Handle unexpected data type.
    default:
      log("Unknown data type: ${data.runtimeType}");
  }
}

You can then use the parseData function to parse your Firebase data:

Object? invalid = 1;

Object? dataList = [1, 2, 3];

Object? dataListOfMap = [
  {
    "key": [1, 2, 3],
  },
];

parseData(invalid);       /// 'Unknown data type: int'.
parseData(dataList);      /// 'Received integer list: [1, 2, 3]'.
parseData(dataListOfMap); /// 'Received list of maps: [{key: [1, 2, 3]}]'.

Dart also provides the is operator to first check if some reference is of a certain type:

if (myInstance is SomeType) ...
offworldwelcome
  • 1,314
  • 5
  • 11
0

For casting lists you should use List<T>.from(list as List), List.cast or List.castFrom.

For casting maps you should use Map<T>.from(map as Map) or Map.castFrom.

class FakeDocumentSnapshot {
  Object? data() {
    return {
      'list': [{}]
    };
  }
}

final snapshot = FakeDocumentSnapshot();

final data = Map<String, dynamic>.from((snapshot.data() ?? {}) as Map);

final list = (List.from(data['list'] ?? [])..removeWhere((e) => e == null));

final result = list
    .map((x) => Map<String, dynamic>.from(x as Map))
    .toList();

(I answered my own question)

JakesMD
  • 1,646
  • 2
  • 15
  • 35
  • This answer is misleading. You should **not** use List.from to cast anything. That creates a copy of the list. Also, `List.castFrom` (and by extension `List.cast`), will still crash if one of the elements of the list is not castable to the type `T`. You still need to verify the proper types before casting, which was your original issue because internally, the `cast` and `castFrom` methods do the `as T` statement which caused you issues. – offworldwelcome Aug 24 '23 at 15:15