Spring Data Elasticsearch custom converters

It happens quite often to have Elasticsearch fields with a small number of possible values so it is natural to model these as enums.

Spring Data maps enum by default using the enum name and it gets a bit complicated when we want to map these values to a human-readable form.

Let's take as example simplified Gender enumeration:

enum class Gender(val esValue: String) {
    MALE("m"),
    FEMALE("f"),
    OTHER("o");
}

We need to write 2 converters, one for serialization and one for deserialization:

@ReadingConverter
class StringToGenderConverter : Converter<String, Gender> {
    private val mapping: Map<String, Gender> = Gender.values().associateBy { it.esValue }

    override fun convert(source: String): Gender {
        return mapping.getOrDefault(source, OTHER)
    }
}

@WritingConverter
class GenderToStringConverter : Converter<Gender, String> {

    override fun convert(source: Gender): String {
        return source.esValue
    }
}

And then we need to add them to configuration:

@Configuration
class ElasticsearchConfig(private val conversionService: GenericConversionService) : AbstractReactiveElasticsearchConfiguration() {

    @Bean
    override fun entityMapper(): EntityMapper {
        val entityMapper = ElasticsearchEntityMapper(elasticsearchMappingContext(), conversionService)
        val converters = listOf(
            GenderToStringConverter(),
            StringToGenderConverter()
        )
        entityMapper.setConversions(ElasticsearchCustomConversions(converters))
        return entityMapper
    }
}

GenderToStringConverter can be made generic by having Gender implement an interface:

enum class Gender(private val esValue: String) : ElasticsearchValue {
    MALE("m"),
    FEMALE("f"),
    OTHER("o");

    override fun esValue(): String = esValue
}

interface ElasticsearchValue {
    fun esValue() : String
}

and converter:

@WritingConverter
class ElasticsearchValueToStringConverter : Converter<ElasticsearchValue, String> {

    override fun convert(source: ElasticsearchValue): String {
        return source.esValue()
    }
}