Encode Map with Enum to JSON
you can use describeEnum method inside foundation.dart
This is really not a solution I would recommend but I ended up doing it mostly for "fun". I don't guarantee anything about the solution besides the fact that it is horrible. :)
The problem is that enum
is not defined as a valid type for Json so the whole concept does give us some problems. One solution is to translate enum
values into String
with the name of the enum first, and then the name of value like MyFirstEnum.first1
. This representation is what Dart gives you if calling toString()
on a enum
value.
This is fine but for safety we could also add a magic string in the beginning like DART_ENUM:MyFirstEnum.first1
so it is easier to recognize between other strings which could have the same name as the enum
value without being an enum.
Next is type safety. In Json, we know that all maps has String
as the type of keys. By making our own representation of enum
and allowing it to also be keys, we cannot expect a decoder to return e.g. Map<String, dynamic>
but must return Map<dynamic, dynamic>
.
With that said, here is my attempt to build a Json decoder and encoder which handles enum
values. It also works for enum
keys in maps:
import 'dart:convert';
class JsonConverterWithEnumSupport {
final String magicString;
final Set<Object> allEnumValues = {};
final Map<String, Object> enumStringToEnumValue = {};
JsonConverterWithEnumSupport(List<List<Object>>? enumsValues,
{this.magicString = "DART_ENUM:"}) {
enumsValues?.forEach(addEnumValues);
}
void addEnumValues(List<Object> enumValues) {
for (final enumValue in enumValues) {
enumStringToEnumValue[enumValue.toString()] = enumValue;
allEnumValues.add(enumValue);
}
}
String _addMagic(dynamic enumValue) => '$magicString$enumValue';
String _removeMagic(String string) => string.substring(magicString.length);
String encode(Object? value) =>
json.encode(value, toEncodable: (dynamic object) {
if (object is Map) {
return object.map<dynamic, dynamic>((dynamic key, dynamic value) =>
MapEntry<dynamic, dynamic>(
allEnumValues.contains(key) ? _addMagic(key) : key,
allEnumValues.contains(value) ? _addMagic(value) : value));
}
if (object is List) {
return object.map<dynamic>(
(dynamic e) => allEnumValues.contains(e) ? _addMagic(e) : e);
}
if (allEnumValues.contains(object)) {
return _addMagic(object);
}
return object;
});
dynamic decode(String source) => json.decode(source, reviver: (key, value) {
if (value is String && value.startsWith(magicString)) {
return enumStringToEnumValue[_removeMagic(value)];
}
if (value is Map) {
return value.map<dynamic, dynamic>((dynamic key, dynamic value) =>
MapEntry<dynamic, dynamic>(
(key is String) && key.startsWith(magicString)
? enumStringToEnumValue[_removeMagic(key)]
: key,
value));
}
return value;
});
}
enum MyFirstEnum { first1, first2 }
enum MySecondEnum { second1, second2 }
void main() {
final converter =
JsonConverterWithEnumSupport([MyFirstEnum.values, MySecondEnum.values]);
final jsonString = converter.encode({
MyFirstEnum.first1: [MySecondEnum.second2, MySecondEnum.second1],
'test': {MyFirstEnum.first2: 5}
});
print(jsonString);
// {"DART_ENUM:MyFirstEnum.first1":["DART_ENUM:MySecondEnum.second2","DART_ENUM:MySecondEnum.second1"],"test":{"DART_ENUM:MyFirstEnum.first2":5}}
print(converter.decode(jsonString));
// {MyFirstEnum.first1: [MySecondEnum.second2, MySecondEnum.second1], test: {MyFirstEnum.first2: 5}}
}
You will need to feed into JsonConverterWithEnumSupport
all the possible enum
values there is possible (see the main
method in the bottom for example).
If you don't want the magic string to be appended on each enum
you can just create JsonConverterWithEnumSupport
with: magicString: ''
as parameter.