I'd like to change incoming/outgoing representatio...
# spring
p
I'd like to change incoming/outgoing representation of an enum in a spring boot app. What do I miss?
Copy code
public enum class Gender(@JsonValue val value: String) {
    MALE("m"), FEMALE("f");
}
The controller:
Copy code
@GetMapping("/test", produces = [org.springframework.http.MediaType.APPLICATION_JSON_VALUE])
    fun gender(@RequestParam gender: Gender): Map<String, Gender> =
        when (gender) {
            Gender.MALE -> mapOf("result" to Gender.FEMALE)
            Gender.FEMALE -> mapOf("result" to Gender.MALE)
        }
For
GET localhost:8080/test?gender=m
I expect to get
{"result": "f"}
.
t
I think
@JsonProperty("f")
is the way2go. https://www.baeldung.com/jackson-serialize-enums
👎🏾 1
j
maybe try
@get:JsonValue
t
maybe, but it should be at each enum value
p
none of them seems to work for me
So currently I have:
t
You can try @get.JsonProperty at the enum values
p
could you show me the exact syntax?
t
Copy code
@get:JsonProperty("m")
        MALE("m"),
Something like this?
p
Not really
j
I would not mix jsonvalue and json property. use one or the other
p
Still cannot figure out, I am poatponing solving it.
e
Its get:JsonValue, no need for JsonProperty
p
Getting back to the problem: So: for serialization, it solves the problem, but not for deserialization.
Copy code
enum class VoltageLevel(@get:JsonValue val s: String) {
    HIGH("h"),
    MEDIUM("m"),
    LOW("l")
}
I use it in the following controller:
Copy code
@RestController
class MapController(private val mapElementRepository: MapElementRepository) {
    @GetMapping("/map-elements/view-window", produces = [MediaType.APPLICATION_JSON_VALUE])
    fun index(
// other parameters
        @RequestParam voltageLevels: Array<VoltageLevel>?
    ): MapElementResult {
// ...
    }
}
On the UI, the values are ok (h, m and v), but when I fire a request, I get Could not resolve parameter [5] in public foobar.mapservice.MapElementResult foobar.mapservice.MapController.index(java.math.BigDecimal,java.math.BigDecimal,java.math.BigDecimal,java.math.BigDecimal,foobar.mapservice.NetworkType[],foobar.mapservice.VoltageLevel,int): Failed to convert value of type 'java.lang.String' to required type 'foobar.mapservice.VoltageLevel'; Failed to convert from type [java.lang.String] to type [@org.springframework.web.bind.annotation.RequestParam foobar.mapservice.VoltageLevel] for value 'h' Same error if I try this way:
Copy code
enum class VoltageLevel {
    @JsonProperty("h")
    HIGH,

    @JsonProperty("m")
    MEDIUM,

    @JsonProperty("l")
    LOW
}
e
AFAIK RequestParams dont use the Jackson HttpMessageConverter. It uses instead a ConversionService. To test this out you can create a simple spring boot test, inject/autowire a
ConversionService
and then try to convert a string from “h” to VoltageLevel. If that fails then you have confirmation that is the issue. And then you can supply your own
Converter<T, U>
to Spring and it will use your custom converter any times it needs to convert from String to VoltageLevel
p
Yes, that works!
Copy code
enum class VoltageLevel(@get:JsonValue val value: String) {
    HIGH("h"), MEDIUM("m"), LOW("l")
}

@Component
class StringToVoltageLevel : Converter<String, VoltageLevel> {
    override fun convert(source: String): VoltageLevel? {
        return VoltageLevel.values().find { it.value == source }
    }
}
Is it possible to generalize it? Say I have some other enums, which also have a string value, and to handle the converting with one converter.
Almost there, but not yet:
Copy code
@Component
public class StringToEnumConverterFactory<E : Enum<*>> : ConverterFactory<String, Enum<*>> {

    private class StringToEnumConverter<T : Enum<T>>(val enumType: Class<T>) :
        Converter<String, T> {

        override fun convert(source: String): T {
            when (enumType) {
                is CustomEnum<*> -> enumType.value
                else -> java.lang.Enum.valueOf(enumType, source.trim())
            }
        }
    }

    override fun <T : Enum<*>> getConverter(targetType: Class<T>): Converter<String, T> {
        return StringToEnumConverter(targetType);
    }
}