Because the documentation doesn't cover this sufficiently it took me a day of researching the flutter sources and trial & error of different things to solve it. So may as well share it.
Golang by default encodes the time.Time in RFC3339 when serializing to Json (like in the given example). Flutter explicitly supports RFC3339, so why doesn't it work? The answer is a small difference in how the seconds fraction part is supported. While Golang produces a precision of 7 digits Dart only supports up to 6 digits and does not gracefully handle violations. So if the example is corrected to only have 6 digits of precision it will parse just fine in Dart:
{
...
"dateCreated": "2018-09-29T19:51:57.413978-07:00",
...
}
In order to solve this in a generic way you have two options: 1. to truncate the additional precision from the string, or 2. implement your own parsing. Let's assume we extend the DateTime
class and create your own CustomDateTime
. The new class has the parse
method overridden to remove all excess after 6 digits before handing it to the parent class' parse method.
Now we can use the CustomDateTime
in our Dart classes. For example:
@JsonSerializable()
class MyObj {
CustomDateTime dateCreated;
MyObj( this.dateCreated);
factory MyObj.fromJson(Map<String, dynamic> json) => _$MyObjFromJson(json);
Map<String, dynamic> toJson() => _$MyObjToJson(this);
}
But of course now the code generation is broken and we get the following error:
Error running JsonSerializableGenerator
Could not generate 'toJson' code for 'dateCreated'.
None of the provided 'TypeHelper' instances support the defined type.
Luckily the json_annotation
package now has an easy solution for us - The JsonConverter
. Here is how to use it in our example:
First define a converter that explains to the code generator how to convert our CustomDateTime
type:
class CustomDateTimeConverter implements JsonConverter<CustomDateTime, String> {
const CustomDateTimeConverter();
@override
CustomDateTime fromJson(String json) =>
json == null ? null : CustomDateTime.parse(json);
@override
String toJson(CustomDateTime object) => object.toIso8601String();
}
Second we just annotate this converter to every class that is using our CustomDateTime data type:
@JsonSerializable()
@CustomDateTimeConverter()
class MyObj {
CustomDateTime dateCreated;
MyObj( this.dateCreated);
factory MyObj.fromJson(Map<String, dynamic> json) => _$MyObjFromJson(json);
Map<String, dynamic> toJson() => _$MyObjToJson(this);
}
This satisfies the code generator and Voila! We can read json with RFC3339 timestamps that come from golang time.Time.