I have a very general question after almost one week of playing around with various tools to de-serialize JSON to Dart and being very frustrated about null safety. The json_serializable package which generates .fromJson and .toJson messages should care about NULL values. But I could not make that work at all! So the one million dollar question is:
Do I have to declare ALL properties of a model as NULLABLE (with a ? e.g. String? myString) if there is any chance that the property may be missing or NULL in the returned JSON ??
When I do NOT declare such members as NULLABLE, I always get this error:
Unhandled Exception: type 'Null' is not a subtype of type 'List<dynamic>' in type cast
These errors always occur in the generated FromJson methods. Here is a sample JSON which causes the problem:
"responseStatus": {
"errorCode": "500",
"message": "This is a test message",
"errors": [
{
"errorCode": "300",
"fieldName": "a field name",
"message": "a message",
"meta": {
"a key": "a value"
}
}
],
"meta": {
"some field": "some value"
}
}
The errors array contains an array of error objects which itself may or may not contain an array of key value pairs (meta). If there are NO errors, the errors array is not submitted as shown here:
"responseStatus": {
"errorCode": "500",
"message": "This is a test message",
"meta": {
"some field": "some value"
}
In such a case the generated DART code crashes as shown below:
ResponseStatus _$ResponseStatusFromJson(Map<String, dynamic> json) =>
ResponseStatus(
errorCode: json['errorCode'] as String? ?? '',
message: json['message'] as String? ?? '',
stackTrace: json['stackTrace'] as String? ?? '',
)
..errors = (json['errors'] as List<dynamic>) // <<==== This is crashing if errors is not in the returned JSON
.map((e) => ResponseError.fromJson(e as Map<String, dynamic>))
.toList()
..meta = Map<String, String>.from(json['meta'] as Map);
The model class I created looks as follows:
@JsonSerializable(explicitToJson: true)
class ResponseStatus extends Equatable {
late final String errorCode;
late final String message;
late final String stackTrace;
late final List<ResponseError> errors;
late final Map<String, String> meta;
ResponseStatus({this.errorCode = '', this.message = '', this.stackTrace = ''}) {
this.errors = [];
this.meta = Map();
}
factory ResponseStatus.fromJson(Map<String, dynamic> json) =>
_$ResponseStatusFromJson(json);
Map<String, dynamic> toJson() => _$ResponseStatusToJson(this);
@override
List<Object?> get props => [errorCode, message, stackTrace, errors, meta];
}
I created a default constructor which initializes all the members (which are NON-nullable) with a value.
If I have to declare all model members as nullable this would be unusable since in my flutter widgets I would have to write thousands of if statements to check if a member is null. I think to avoid this was ONE of the ideas behind sound null safety.
I am curious to learn if this is a bug or how I get this thing working!