Skip to content

Commit

Permalink
Allow Enum self as field when Enum with JsonFormat.Shape.OBJECT (#4565
Browse files Browse the repository at this point in the history
)
  • Loading branch information
JooHyukKim authored Jun 7, 2024
1 parent fc67817 commit ee39f3e
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 12 deletions.
4 changes: 4 additions & 0 deletions release-notes/CREDITS-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -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)
6 changes: 6 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,18 +124,18 @@ 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;
}
// Most likely synthetic fields, if any, are to be skipped similar to methods
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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}.
* <p>
* 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<BeanPropertyDefinition> 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();
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Level> levels = new ArrayList<>();
levels.add(Level.LEVEL1);
levels.add(Level.LEVEL2);
levels.add(Level.LEVEL3);

String JSON = MAPPER.writerFor(new TypeReference<List<Level>>() {
}).writeValueAsString(levels);

// Fails, because we get [{"label":"level1"},{"label":"level2"},{"label":"level3"}]
assertEquals(
"["
+ "{\"label\":\"level1\"},"
+ "{\"label\":\"level2\"},"
+ "{\"label\":\"level3\",\"sublevel\":{\"label\":\"level1\"}}"
+ "]",
JSON);
}
}

0 comments on commit ee39f3e

Please sign in to comment.