javajsonenumsjacksonjackson-databind

Deserialize Map<Enum<?>, Object> in Java with Jackson library


I need to serialize and deserialize in JSON format a Java map using the Jackson library. The map type is Map<Enum<?>, Object> and its purpose is to store a configuration made by key-value pairs, where keys are enum instances coming from a third party library (hence I do not have control over their definition). Keys can be enums of different type, whereas values can be int, double or String. Also, you can assume that different enums have different instance names.

Serialization works fine:

public class Main {
  public enum IntParam {I1, I2, I3}
  public enum DoubleParam {D1, D2, D3}
  public enum StringParam {S1, S2, S3}

  public static void main(final String[] args) {
    Map<Enum<?>, Object> config = Map.of(
        IntParam.I1, 0, 
        DoubleParam.D2, 1.0, 
        StringParam.S3, "sss"
    );
    new ObjectMapper().writeValueAsString(config)
    // {"I1": 1, "D2": 1.0, "S3": "sss"} 
  }

The problem is deserializing the above object:

    String json = "{\"I1\": 1, \"D2\": 1.0, \"S3\": \"sss\"}";
    new ObjectMapper().readValue(json, new TypeReference<Map<Enum<?>, Object>>())

fails because Jackson cannot know which enum contains instances named I1, D2 and S3.

I would like to achieve something like

{
   "com.external.company.IntParam.I1": 1,
   "com.external.company.DoubleParam.D2": 1.0,
   "com.external.company.StringParam.S3": "sss"
}

during serialization and then be able to deserialize it.


Solution

  • Starting from @Chaosfire answer, I achieved exactly what I need.

    Code:

      @JsonSerialize(keyUsing=MyEnumSerializer.class)
      @JsonDeserialize(keyUsing=MyEnumDeserializer.class)
      public static class MyParamMap extends HashMap<Enum<?>, Object> {};
    
      public static class MyEnumSerializer extends JsonSerializer<Enum<?>> {
        @Override
        public void serialize(final Enum<?> value, final JsonGenerator gen, final SerializerProvider serializers) throws IOException {
          gen.writeFieldName(value.getClass().getName() + "." + value.name());
        }
      }
    
      public static class MyEnumDeserializer extends KeyDeserializer {
        @Override
        public Object deserializeKey(final String key, final DeserializationContext ctxt) {
          final int index = key.lastIndexOf(".");
          final String className = key.substring(0, index);
          try {
            final Class<Enum> enumClass = (Class<Enum>) Class.forName(className);
            return Enum.valueOf(enumClass, key.substring(index + 1));
          } catch (final ClassCastException | ClassNotFoundException e) {
            // should not happen
            throw new RuntimeException(e);
          }
        }
      }
    

    Usage:

      public static void main(final String[] args) throws StreamWriteException, DatabindException, IOException {
        MyParamMap params = new MyParamMap();
        params.put(IntParam.I1, 1);
        params.put(DoubleParam.D2, 1.1);
        params.put(StringParam.S3, "sss");
    
        final Path path = Path.of("params.json");
        final ObjectMapper mapper = new ObjectMapper();
        mapper.writeValue(path.toFile(), params);
        params = mapper.readValue(path.toFile(), MyParamMap.class);
        System.out.println(params);
      }