Working with JSON in Dart

Working with JSON in Dart is relatively easy, but there are still a few caveats.

Handling dynamic types

There's a page dedicated to JSON in Dart at https://dart.dev/guides/json. The first way is to use dart:convert's jsonDecode(String) => dynamic.

I was trying to deserialize this kind of JSON:

[
  {"isoCode": "ab", "name": "Abkhazian"},
  {"isoCode": "aa", "name": "Afar"},
  ...
]

And someone had already implemented a deserializer:

class Language {
  Language(this.isoCode, this.name);

  final String name;
  final String isoCode;

  Language.fromMap(Map<String, String> map)
      : name = map['name']!,
        isoCode = map['isoCode']!;
}

My first attempt looked like this:

  File('lib/languages.json').readAsString().then((String contents) {
    final jsonLanguages = jsonDecode(contents);
    final languages = jsonLanguages.map((m) => Language.fromMap(m));
    print(languages.runtimeType);
    print(languages);
  });

But it failed when running Language.fromMap(m), saying:

Unhandled exception: type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'Map<String, String>'

So I forcefully converted m into a Map<String, String>: Map<String, String>.from(m). Now the code executed but languages was not a List, nor did it contain Languages:

// MappedListIterable<dynamic, dynamic>
print(languages.runtimeType);
// (Instance of 'Language', Instance of 'Language', Instance of 'Language', ...)
print(languages);

When I hover over languages, VSC simply tells me it's dynamic.

I expected it to tell me Map<String, Language> or something of the sort

To convert a Map to a List, I can use toList(). But since VSC doesn't know the result of dynamic.map(...) is a Map, it doesn't know that toList() is available.

VSC doesn't know toList() is available

Now when I run it, I get the following output:

List<dynamic>
[Instance of 'Language', Instance of 'Language', Instance of 'Language', Instance of 'Language', Instance of 'Language', Instance of 'Language', Instance of 'Language', Instance of 'Language', ...]

If I try to forcefully type it to List<Language> like this:

final List<Language> languages = jsonLanguages
    .map((m) => Language.fromMap(Map<String, String>.from(m)))
    .toList();

I get this error:

Unhandled exception: type 'List<dynamic>' is not a subtype of type 'List<Language>'

The fix is to specify the type when calling map, as suggested here. Now here's the final code snippet:

  File('lib/languages.json').readAsString().then((String contents) {
    final jsonLanguages = jsonDecode(contents);
    final List<Language> languages = jsonLanguages
        .map<Language>((m) => Language.fromMap(Map<String, String>.from(m)))
        .toList();
    print(languages.runtimeType);
    print(languages);
  });

And the result:

List<Language>
[Instance of 'Language', Instance of 'Language', Instance of 'Language', Instance of 'Language', Instance of 'Language', Instance of 'Language', Instance of 'Language', Instance of 'Language', Instance of 'Language', Instance of 'Language'...]