Suppose I have the following model (using freezed):
@freezed
class User with _$User {
const factory User({
required UserID id,
required List<String> categories,
}) = _User;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
I'd like to deserialize this model from a JSON object that looks like this:
{
"id": "1234",
"categories": {
"A": true,
"B": true
}
}
Obviously the default serialization does not work, so I tried implementing a custom JsonConverter
:
class ListMapConverter<K, V> implements JsonConverter<List<K>, Map<K, V>> {
const ListMapConverter(this._value);
final V _value;
@override
List<K> fromJson(Map<K, V> json) => json.keys.toList();
@override
Map<K, V> toJson(List<K> object) => {for (final e in object) e: _value};
}
I annotated the model's property like so:
@ListMapConverter<String, bool>(true) required List<String> categories
This does not work, and the custom converter is not used in the generated output:
_$UserImpl _$$UserImplFromJson(Map<String, dynamic> json) => _$UserImpl(
id: UserID.fromJson(json['id'] as String),
categories: (json['categories'] as List<dynamic>)
.map((e) => e as String)
.toList(),
);
Map<String, dynamic> _$$UserImplToJson(_$UserImpl instance) =>
<String, dynamic>{
'id': instance.id,
'categories': instance.categories,
};
How can I fix this? Is there a better way to convert Maps to Lists and vice versa using freezed/json_serializable? Thanks.
The problem seems to be json_serializable
silently ignoring your generic converter. On top of that, json_serializable
does not support converters with constructor arguments, see this issue.
A workaround would be to make your converter non-generic and use named constructors to provide the instance variable _value
:
class ListMapConverter implements JsonConverter<List<String>, Map<String, bool>> {
// const ListMapConverter(this._value);
const ListMapConverter.zero() : _value = false;
const ListMapConverter.one() : _value = true;
final bool _value;
@override
List<String> fromJson(Map<String, bool> json) => json.keys.toList();
@override
Map<String, bool> toJson(List<String> object) => {for (final e in object) e: _value};
}
You could then use the annotation: @ListMapConverter.one()
.
In view of all this, it might be easier to extend the class User
manually by adding the method toJson
and the factory constructor fromJson
.
You could still use freezed
to generate other convenience methods, like copyWith
etc.