Reputation: 4832
I want to encode my Map to a json. It looks like this:
Map<MyEnum, int> map = {type: limit};
Where MyEnum
is an enum. A simple json.encode(map)
won't work as it does not know how to serialize the enum class I guess.
The error message is:
Unhandled Exception: Converting object to an encodable object failed: _LinkedHashMap len:1
How can I manage to serialize this map to a json?
Upvotes: 1
Views: 3777
Reputation: 161
enum OrderState { pending, filled, triggered, cancelled }
extension OrderStateExt on OrderState {
String get value {
switch (this) {
case OrderState.pending:
return "PENDING";
case OrderState.filled:
return "FILLED";
case OrderState.triggered:
return "TRIGGERED";
case OrderState.cancelled:
return "CANCELLED";
}
}
}
class OrderRequest {
OrderState state;
OrderRequest({required this.state});
Map<String, dynamic> toMap() {
return {
'state': state.value,
};
}
}
String toJson() => json.encode(toMap());
factory OrderRequest.fromMap(Map<String, dynamic> map) {
return OrderRequest (
state: OrderState.values
.where((e) => e.value == map['state']).first
);
}
factory OrderRequest.fromJson(String source) =>
OrderRequest.fromMap(json.decode(source));
Upvotes: 0
Reputation: 945
you will need a function to convert string to enum:
T enumFromString<T>(List<T> values, String value) {
return values.firstWhere((v) => v.toString().split('.')[1] == value, orElse: () => null);
}
your enum is
enum MyEnum {type, destype};
suppose it is used as map inside a class to serialize and deserialize:
class MyClass {
Map<MyEnum, int> myProperty = {type: 1};
// serialize
Map<String, dynamic> toJson() {
return {
'myProperty': myProperty.map((key, value) => MapEntry(key.name, value)),
}
// deserialize
MyClass.fromJson(Map<String, dynamic> json) {
myProperty=
json['myProperty'].map((k, v) => MapEntry(enumFromString<MyEnum>(MyEnum.values, k), v)).cast<MyEnum, int>();
}
}
Upvotes: 0
Reputation: 31309
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.
Upvotes: 1
Reputation: 7328
You could create an extension on your enum to convert it to a String then convert your map to a Map<String, int>
so it will be encoded correctly:
import 'dart:convert';
enum MyEnum { type }
extension MyEnumModifier on MyEnum {
String get string => this.toString().split('.').last;
}
void main() {
Map<MyEnum, int> map = {MyEnum.type: 10};
Map<String, int> newMap = {};
map.forEach((key, value) =>
newMap[key.string] = value);
final json = jsonEncode(newMap);
print(json);
}
Output
{"type":10}
Upvotes: 1