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.