Search code examples
javajsonenumsjackson

Customize Jackson serialization/deserialization of child attribute from parent


I have a generic record that I use to send enum values with a description to my front-end:

public record EnumJson<E extends Enum<E>>(E value, String description) {
}

I have a single endpoint in which I provide many different enums in one response. It looks a bit like this (but more complex):

public record Enums(
  List<YesNo> yesNo, 
  List<RedBlue> redBlue, 
  List<UpDown> upDown, ...) {
}

For many of these enums, Jackson's default serialization mechanism of this E is fine (serialize using Enum.name()). However, for a handful of these enums, I'd like to customize serialization. I'm looking for some method to control the way E is serialized from Enums. Perhaps with an annotation like:

public record Enums(
  List<YesNo> yesNo, 
  List<RedBlue> redBlue, 
  List<@SomeInstructionToCustomizeSerialization UpDown> upDown, ...) {
}

I currently have a copy of EnumJson as follows:

public record SpecialEnumJson<E extends Enum<E>>(@JsonSerialize(...) E value, String description) {
}

However, it feels like there must be a better alternative.

Things that are not a solution in my case:

  • Add a custom @JsonValue to those handful of enums, because this would have an effect on how these enums are serialized throughout my entire application (which I do not want)

Solution

  • If you need a custom serialization/deserialization logic only for some enum fields, then you could still employ the StdDeserializer and StdSerializer classes, customize the logic specifically for those fields, and finally add the @JsonSerialize and @JsonDeserialize annotations before the interested attributes.

    In your case, a customization for the List<UpDown> field in Enums could look like this:

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Enums {
        List<YesNo> yesNo;
        List<RedBlue> redBlue;
        @JsonSerialize(using = SerializerListUpDown.class)
        @JsonDeserialize(using = DeserializerListUpDown.class)
        List<UpDown> upDown;
    }
    
    public class SerializerListUpDown extends StdSerializer<List<UpDown>> {
    
        public SerializerListUpDown() {
            this(null);
        }
    
        public SerializerListUpDown(Class<List<UpDown>> l) {
            super(l);
        }
    
        @Override
        public void serialize(List<UpDown> value, JsonGenerator gen, SerializerProvider provider) throws IOException {
            int[] upDownValues = value.stream().mapToInt(UpDown::getId).toArray();
            gen.writeArray(upDownValues, 0, upDownValues.length);
        }
    }
    
    public class DeserializerListUpDown extends StdDeserializer<List<UpDown>> {
    
        public DeserializerListUpDown() {
            this(null);
        }
    
        public DeserializerListUpDown(Class<List<UpDown>> vc) {
            super(vc);
        }
    
        @Override
        public List<UpDown> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
            ObjectCodec codec = p.getCodec();
            JsonNode root = codec.readTree(p);
            List<UpDown> list = new ArrayList<>();
            for (int i = 0; i < root.size(); i++) {
                for (UpDown e : UpDown.values()) {
                    if (root.get(i).asInt() == e.getId()) {
                        list.add(e);
                    }
                }
            }
            return list;
        }
    }
    

    Demo

    Here is also a demo at OneCompiler. As you can see from the output, the annotations manage to customize the serialization and deserialization for a List<UpDown> field by using the enum's value instead of its name.

    Json => {"yesNo":["YES","NO","YES"],"redBlue":["BLUE","RED","RED"],"upDown":[2,2,8]}
    Object => Enums{yesNo=[YES, NO, YES], redBlue=[BLUE, RED, RED], upDown=[DOWN, DOWN, UP]}