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.
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.
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'...]