Using StandardJsonPlugin with built_value for Flutter
Or why do I get type '_InternalLinkedHashMap' is not a subtype of type 'Iterable' in type cast
error when deserializing JSON objects using built_value
.
TL;DR jump to the solution.
The setup
When using built_value in a project, what I find useful is to use the JSON to built_value tool in order to create my model from a JSON I already have from the REST API.
For our example let’s use the default JSON the tool prefills.
{
"id": 157538,
"date": "2017-07-21T10:30:34",
"date_gmt": "2017-07-21T17:30:34",
"type": "post",
"link": "https://example.com",
"title": {
"rendered": "Json 2 dart built_value converter"
},
"tags": [ 1798, 6298 ]
}
This JSON will produce the following model.
In addition, you have to register the above model types in your Serializers
instance.
Run the generator
Once everything is setup you need to run the built_value
's generator to generate the .g.dart
files with the following command:
flutter packages pub run build_runner build
The error
After you have successfully generated the serialization code you can test it out with a very simple test.
void main() {
test('should deserialize sucessfully', () {
final json = "{\"id\":157538,\"date\":\"2017-07-21T10:30:34\",\"date_gmt\":\"2017-07-21T17:30:34\",\"type\":\"post\",\"link\":\"https://example.com\",\"title\":{\"rendered\":\"Json 2 dart built_value converter\"},\"tags\":[1798,6298]}"; RootDto.fromJson(json);
});
}
Running the above test will produce the following, infamous by now, error:
Deserializing '{id: 157538, date: 2017-07-21T10:30:34, date_gmt: 2017-07-21T17:30:34, type: ...' to 'RootDto' failed due to: type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'Iterable<dynamic>' in type cast
The cause
If you follow the stack you will find that it originates from in BuiltJsonSerializers._deserialize
method and more specifically in line 153 of package:built_value/src/built_json_serializers.dart
. In that line, the library tries to cast the object as an Iterable
, but it finds an _InternalLinkedHashMap
.
serializer.deserialize(this, object as Iterable, specifiedType: specifiedType);
Add the StandardJsonPlugin
to the Serializers
.
final Serializers serializers =
(_$serializers.toBuilder()
..addPlugin(StandardJsonPlugin())).build();
This plugin will actually convert the Map
to a list in its beforeDeserialize
method. This way the serializer will always have an Iterable
to process and will not fail the cast.
Bottom line, always register the StandardJsonPlugin
in your Serializers
instance, when you are deserializing a JSON you got over the wire.