简体   繁体   中英

Jackson @JsonValue annotation selectively override or disable

I have an enum with the following attributes:

private Integer id;
private String name;
private String description;

This enum has a function with Jackson's @JsonValue annotation:

@JsonValue
public String toValue() {
    return Stream.of(values())
            .filter(eventType -> eventType == this)
            .findAny()
            .map(EventType::toString)
            .orElseThrow(() -> new IllegalArgumentException("Unable to convert EventType to value:" + this));
}

This performs as desired, serializing the enum values to just the value returned by toString, which is the name of the enum.

I want to be able to disable the @JsonValue annotation and just use Jackson's otherwise default JSON serialization behavior attached to this class: @JsonFormat(shape = JsonFormat.Shape.OBJECT) in certain cases where the object representing the entire enum must be fetched, instead of just the name.

Jackson does not appear to have this ability built in (either that or I do not understand it well enough). I cannot create a wrapper class that extends this class, as Java does not allow that with enums.

Any suggestions?

This can be achieved using a Mixin.

Keep your enum class the same, create a mixin class:

// MyEnumMixin.java

import com.fasterxml.jackson.annotation.JsonValue;

public abstract class MyEnumMixin {

    @JsonValue(false)
    public abstract String toValue();
}

Then add the mixin when you create your Mapper

// MyService.java

...
    ObjectMapper mapper = new ObjectMapper();
    mapper.addMixIn(MyEnum.class, MyEnumMixin.class);
    return mapper.writeValueAsString(pojoWithEnum);
...


Credit to https://stackoverflow.com/users/6911095/ldz for helping me solve something similar and being able to add an answer here. https://stackoverflow.com/a/59459177/7589862

I don't think of a ready made solution for this, but the closest option I can think of is to use a custom serializer for this specific enum type.

Adding a custom serializer is easy and just requires one to extend JsonSerializer class and implement serialize method, then use the class in the @JsonSerialize annotation to the type where needed (and you don't need @JsonValue ). Enum object would be passed into the serialize method and there you can choose to write either toString or toValue .

An example code is provided below along with the output.

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.AllArgsConstructor;
import lombok.Data;

import java.io.IOException;
import java.util.stream.Stream;

public class SOQuestion {

    static ThreadLocal<Boolean> USE_TO_STRING = ThreadLocal.withInitial(() -> false);

    @JsonSerialize(using = MySerializer.class)
    enum EventType{
        ONE(1, "One", "Just One"), TWO(2, "Two", "Just Two");

        private Integer id;
        private String name;
        private String description;

        EventType(Integer id, String name, String description) {
            this.id = id;
            this.name = name;
            this.description = description;
        }

        public String toValue() {
            return Stream.of(values())
                    .filter(eventType -> eventType == this)
                    .findAny()
                    .map(EventType::toString)
                    .map(s -> "toValue="+s)
                    .orElseThrow(() -> new IllegalArgumentException("Unable to convert EventType to value:" + this));
        }
    }

    @Data
    @AllArgsConstructor
    static class Dummy{
        int someNum;
        EventType eventType;
    }

    public static void main(String[] args) throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        Dummy dummy = new Dummy(100, EventType.ONE);
        System.out.println("**** DEFAULT *****");
        System.out.println(objectMapper.writeValueAsString(dummy));
        System.out.println("**** toString *****");
        USE_TO_STRING.set(true);
        System.out.println(objectMapper.writeValueAsString(dummy));
    }

    private static class MySerializer extends JsonSerializer<EventType> {
        @Override
        public void serialize(EventType value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
            if(USE_TO_STRING.get()){
                gen.writeString(value.toString());
            }else{
                gen.writeString(value.toValue());
            }
        }
    }
}

Output:

**** DEFAULT *****
{"someNum":100,"eventType":"toValue=ONE"}
**** toString *****
{"someNum":100,"eventType":"ONE"}

Apologies for using lombok which makes things simple. If you're not familiar, its time to pick it up.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM