From ee39f3eaeab2875af51f37605d19145adf338673 Mon Sep 17 00:00:00 2001 From: "Kim, Joo Hyuk" Date: Sat, 8 Jun 2024 03:07:32 +0900 Subject: [PATCH] Allow Enum self as field when Enum with `JsonFormat.Shape.OBJECT` (#4565) --- release-notes/CREDITS-2.x | 4 ++ release-notes/VERSION-2.x | 6 ++ .../introspect/AnnotatedFieldCollector.java | 10 +-- .../databind/ser/BasicSerializerFactory.java | 18 +++--- .../ser/enums/EnumAsFormatObject4564Test.java | 61 +++++++++++++++++++ 5 files changed, 87 insertions(+), 12 deletions(-) create mode 100644 src/test/java/com/fasterxml/jackson/databind/ser/enums/EnumAsFormatObject4564Test.java diff --git a/release-notes/CREDITS-2.x b/release-notes/CREDITS-2.x index 500acbc782..1330e64995 100644 --- a/release-notes/CREDITS-2.x +++ b/release-notes/CREDITS-2.x @@ -1738,3 +1738,7 @@ Stephane Bailliez (sbailliez@github) * Reported #4409: Deserialization of enums with name defined with different cases leads to `InvalidDefinitionException`: Multiple fields representing property (2.16.2) + +Guillaume Jardillier (Mugiwara84@github) + * Reported #4564: Possible 2.16.0 Enum-as-JSON-Object serialization regression + (2.16.3) diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index 66ab6457eb..9091e73120 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -8,6 +8,12 @@ Project: jackson-databind - +2.16.3 (not yet released) + +#4564: Possible 2.16.0 Enum-as-JSON-Object serialization regression + (reported by Guillaume J) + (fix contributed by Joo-Hyuk K) + 2.16.2 (09-Mar-2024) #4302: Problem deserializing some type of Enums when using `PropertyNamingStrategy` diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedFieldCollector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedFieldCollector.java index f56081c35c..0917ccaf70 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedFieldCollector.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/AnnotatedFieldCollector.java @@ -124,7 +124,8 @@ private void _addFieldMixIns(Class mixInCls, Class targetClass, private boolean _isIncludableField(Field f) { - // [databind#2787]: Allow `Enum` mixins + // [databind#2787]: To allow `Enum` mixins, need to include Enum constants even + // to they are static fields, not instance ones. if (f.isEnumConstant()) { return true; } @@ -132,10 +133,9 @@ private boolean _isIncludableField(Field f) if (f.isSynthetic()) { return false; } - // Static fields are never included. Transient are (since 2.6), for - // purpose of propagating removal - int mods = f.getModifiers(); - if (Modifier.isStatic(mods)) { + // Static fields are never included (except for above-mentioned Enum constants. + // Transient are (since 2.6), for purpose of propagating removal + if (Modifier.isStatic(f.getModifiers())) { return false; } return true; diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java index bcccd9eaa0..825474fd34 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/BasicSerializerFactory.java @@ -1208,7 +1208,7 @@ protected JsonSerializer buildEnumSerializer(SerializationConfig config, ((BasicBeanDescription) beanDesc).removeProperty("declaringClass"); // [databind#2787]: remove self-referencing enum fields introduced by annotation flattening of mixins if (type.isEnumType()){ - _removeEnumSelfReferences(((BasicBeanDescription) beanDesc)); + _removeEnumSelfReferences(beanDesc); } // returning null will mean that eventually BeanSerializer gets constructed return null; @@ -1226,24 +1226,28 @@ protected JsonSerializer buildEnumSerializer(SerializationConfig config, } /** - * Helper method used for serialization {@link Enum} as {@link JsonFormat.Shape#OBJECT}. Removes any - * self-referencing properties from its bean description before it is transformed into a JSON Object - * as configured by {@link JsonFormat.Shape#OBJECT}. + * Helper method used for serialization {@link Enum} as {@link JsonFormat.Shape#OBJECT}. + * Removes any self-referencing properties from its bean description before it is + * transformed into a JSON Object as configured by {@link JsonFormat.Shape#OBJECT}. *

- * Internally, this method iterates through {@link BeanDescription#findProperties()} and removes self. + * Internally, this method iterates through {@link BeanDescription#findProperties()} + * and removes self references. * * @param beanDesc the bean description to remove Enum properties from. * * @since 2.16 */ - private void _removeEnumSelfReferences(BasicBeanDescription beanDesc) { + private void _removeEnumSelfReferences(BeanDescription beanDesc) { Class aClass = ClassUtil.findEnumType(beanDesc.getBeanClass()); Iterator it = beanDesc.findProperties().iterator(); while (it.hasNext()) { BeanPropertyDefinition property = it.next(); JavaType propType = property.getPrimaryType(); // is the property a self-reference? - if (propType.isEnumType() && propType.isTypeOrSubTypeOf(aClass)) { + if (propType.isEnumType() && propType.isTypeOrSubTypeOf(aClass) + // [databind#4564] Since 2.16.3, Enum's should allow self as field, so let's remove only if static. + && property.getAccessor().isStatic()) + { it.remove(); } } diff --git a/src/test/java/com/fasterxml/jackson/databind/ser/enums/EnumAsFormatObject4564Test.java b/src/test/java/com/fasterxml/jackson/databind/ser/enums/EnumAsFormatObject4564Test.java new file mode 100644 index 0000000000..60b69f25bd --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/ser/enums/EnumAsFormatObject4564Test.java @@ -0,0 +1,61 @@ +package com.fasterxml.jackson.databind.ser.enums; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +// [databind#4564] Fix Enum-asJSON-Object serialization with self as field. +public class EnumAsFormatObject4564Test +{ + + @JsonFormat(shape = JsonFormat.Shape.OBJECT) + @JsonInclude(JsonInclude.Include.NON_NULL) + public enum Level { + LEVEL1("level1"), + LEVEL2("level2"), + LEVEL3("level3", Level.LEVEL1); + + public String label; + public Level sublevel; + + Level(String level2) { + this.label = level2; + } + + Level(String level3, Level level) { + this.label = level3; + this.sublevel = level; + } + } + + private final ObjectMapper MAPPER = new JsonMapper(); + + @Test + public void testEnumAsFormatObject() throws JsonProcessingException { + List levels = new ArrayList<>(); + levels.add(Level.LEVEL1); + levels.add(Level.LEVEL2); + levels.add(Level.LEVEL3); + + String JSON = MAPPER.writerFor(new TypeReference>() { + }).writeValueAsString(levels); + + // Fails, because we get [{"label":"level1"},{"label":"level2"},{"label":"level3"}] + assertEquals( + "[" + + "{\"label\":\"level1\"}," + + "{\"label\":\"level2\"}," + + "{\"label\":\"level3\",\"sublevel\":{\"label\":\"level1\"}}" + + "]", + JSON); + } +}