How can I make Jackson serialize enum constants to lower-case values if the enum is read-only?

73 Views Asked by At

I ask this because of one bug in Swagger UI. More on that is here.

Here, a small MRE:

package com.example.dynamicgateway.misc;

import com.fasterxml.jackson.annotation.JsonValue;
import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@Getter
class SomeClass {
    SomeEnum someEnumProperty = SomeEnum.INSTANCE_ONE;

    enum SomeEnum {
        INSTANCE_ONE, INSTANCE_TWO;
    }
}
package com.example.dynamicgateway.misc;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Test;

public class GenericTest {
    @Test
    @SneakyThrows
    void test() {
        ObjectMapper objectMapper = new ObjectMapper();
        SomeClass someClass = new SomeClass();
        String serializedSomeClass = objectMapper.writeValueAsString(someClass);
        System.out.println(serializedSomeClass);
    }
}
{"someEnumProperty":"INSTANCE_ONE"}

Now, let's imagine I need the enum to be serialized into instance_one instead. If I have write access to SomeClass, I could do this:

@NoArgsConstructor
@Getter
class SomeClass {
    SomeEnum someEnumProperty = SomeEnum.INSTANCE_ONE;

    enum SomeEnum {
        INSTANCE_ONE, INSTANCE_TWO;

        @Override
        @JsonValue // this is the key
        public String toString() {
            return name().toLowerCase();
        }
    }
}
{"someEnumProperty":"instance_one"}

But what if I don't have such a write access to SomeClass? Maybe, I can somehow configure my ObjectMapper, can't I?

2

There are 2 best solutions below

2
Rob Spoor On BEST ANSWER

Continuing from https://stackoverflow.com/a/77977001/1180351:

If it's for serialization only, you can create a single serializer class that you can reuse for multiple enums:

public class LowerCaseEnumSerializer extends JsonSerializer<Enum<?>> {
    @Override
    public void serialize(Enum<?> value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeString(value.name().toLowerCase());
    }
}

Don't override handledType; instead specify the type when the serializer is registered:

ObjectMapper objectMapper = new ObjectMapper();
SimpleModule lowerCaseEnumModule = new SimpleModule();
LowerCaseEnumSerializer enumSerializer = new LowerCaseEnumSerializer();
// for each enum type
lowerCaseEnumModule.addSerializer(SomeEnum.class, enumSerializer);

objectMapper.registerModule(lowerCaseEnumModule);
0
Sergey Zolotarev On

It is clumsy, but it works. Also, if you have a bunch of read-only enums, it's going to be a bit tedious to write and add all those custom serializers.

package com.example.dynamicgateway.misc;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Test;

public class GenericTest {
    @Test
    @SneakyThrows
    void test() {
        ObjectMapper objectMapper = new ObjectMapper();
        SimpleModule someEnumModule = new SimpleModule();
        SomeEnumSerializer enumSerializer = new SomeEnumSerializer();
        someEnumModule.addSerializer(enumSerializer);
        objectMapper.registerModule(someEnumModule);
        SomeClass someClass = new SomeClass();
        String serializedSomeClass = objectMapper.writeValueAsString(someClass);
        System.out.println(serializedSomeClass);
    }
}
package com.example.dynamicgateway.misc;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

import java.io.IOException;

public class SomeEnumSerializer extends JsonSerializer<SomeClass.SomeEnum> {
    @Override
    public void serialize(SomeClass.SomeEnum someEnum, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeString(someEnum.toString());
    }

    @Override
    public Class<SomeClass.SomeEnum> handledType() {
        return SomeClass.SomeEnum.class;
    }
}
{"someEnumProperty":"instance_one"}