Issue
I was working with SpringBoot 2.2.6, Elastic Search 5.6.13 and I decided to upgrade to SpringBoot 2.4.1, ElasticSearch 7.10. I'm storing complex documents in ES, with Enums as values and map keys. For all the serialization between Enums and strings (plus custom serializers) I'm using a custom entity mapper with a custom configured Jackson ObjectMapper.
In ES 4, the ElasticsearchEntityMapper interface has been removed. And all conversions should be implemented as converters. So I implemented converters for each enum, and it writes correctly into ES.
But they are not being read correctly. I found several problems but I would like to start with one specific. The map keys are not being translated back to Enums. They are being read as string.
I debugged the Spring Boot code looking for a specific interface I should be implementing but I couldn't find any.
Any ideas?
Solution
I've managed to solve this by writing custom MappingElasticsearchConverter
public class CustomMappingElasticsearchConverter extends MappingElasticsearchConverter {
public CustomMappingElasticsearchConverter(MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
super(mappingContext);
}
@Override
@Nullable
protected <R> R readValue(@Nullable Object source, ElasticsearchPersistentProperty property, TypeInformation<R> targetType) {
R r = super.readValue(source, property, targetType);
return convertMapKeysToEnum(r, targetType);
}
@SuppressWarnings("unchecked")
private <R> R convertMapKeysToEnum(R value, TypeInformation<R> targetType) {
if (!(value instanceof Map)) return value;
Class<?> keyClass = targetType.getTypeArguments().get(0).getType();
if (!Enum.class.isAssignableFrom(keyClass)) return value;
return (R) ((Map<?, ?>) value).entrySet().stream()
.collect(toMap(e -> Enum.valueOf((Class<Enum>) keyClass, e.getKey().toString()), e -> e.getValue()));
}
}
Then just replace the default bean with your custom implementation
public class ElasticsearchConfig extends AbstractElasticsearchConfiguration {
...
@Bean
@Nonnull
@Override
public ElasticsearchConverter elasticsearchEntityMapper(SimpleElasticsearchMappingContext elasticsearchMappingContext) {
MappingElasticsearchConverter elasticsearchConverter = new CustomMappingElasticsearchConverter(
elasticsearchMappingContext);
elasticsearchConverter.setConversions(elasticsearchCustomConversions());
return elasticsearchConverter;
}
}
Answered By - Mike Mike