Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Possible 2.16.0 Enum-as-JSON-Object serialization regression #4564

Closed
cowtowncoder opened this issue Jun 5, 2024 Discussed in #4563 · 10 comments
Closed

Possible 2.16.0 Enum-as-JSON-Object serialization regression #4564

cowtowncoder opened this issue Jun 5, 2024 Discussed in #4563 · 10 comments
Labels
enum Related to handling of Enum values
Milestone

Comments

@cowtowncoder
Copy link
Member

Discussed in #4563

Originally posted by Mugiwara84 June 5, 2024
Hi,
I am using jackson with spring to serialize a java enumeration which looks like this :

@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum Level {
  LEVEL1("level1"),
  LEVEL2("level2"),
  LEVEL3("level3", Level.LEVEL1);

  String label;
  Level sublevel;

.......
}

Before updating to 2.16.0, I was getting this when serializing :

[
  {
    "label": "level1"
  },
  {
    "label": "level2"
  },
  {
    "label": "level3",
    "sublevel": {
        "label": "level1"
    }
  }
]

Since 2.16.0, I'm getting :

[
  {
    "label": "level1"
  },
  {
    "label": "level2"
  },
  {
    "label": "level3"
  }
]

Is this the expected behaviour ?
Is there a different way to achieve the previous result with parameters or annotations ?

@cowtowncoder cowtowncoder added the enum Related to handling of Enum values label Jun 5, 2024
@JooHyukKim
Copy link
Member

We do need Mapper configuration and rest of the enum declaration, to find out how the sublevel property is ignored, but in LEVEL3 is found.

Meanwhile refer to below failing reproduction.

public class EnumAsFormatObject4564Test
        extends BaseMapTest {

    @JsonFormat(shape = JsonFormat.Shape.OBJECT)
    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 = newJsonMapper();

    @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);
    }
}

@Mugiwara84
Copy link

I do not have Mapper configuration, I am just using Spring which uses jackson when returning response in a controller from a List.

But I did recreate a simple java maven project using this pom :

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>level</groupId>
	<artifactId>level</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<dependencies>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>2.15.4</version>
		</dependency>
		<dependency>
			<groupId>org.junit.jupiter</groupId>
			<artifactId>junit-jupiter</artifactId>
			<version>5.10.2</version>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.13.0</version>
				<configuration>
					<release>21</release>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

When I am using your code (except I am not extending BaseMapTest and I add the @JsonInclude(JsonInclude.Include.NON_NULL) annotation) :

import static org.junit.jupiter.api.Assertions.assertEquals;

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 java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;

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);
  }
}

The test is ok. But when I update njackson-databind to 2.16.0 or 2.17.1, the test fails, it returns :
[{"label":"level1"},{"label":"level2"},{"label":"level3"}]

@pjfanning
Copy link
Member

pjfanning commented Jun 6, 2024

There are some changes in the EnumSerializer for Jackson 2.16 - eg #4039

@JooHyukKim
Copy link
Member

This is regression created by #3832 which improved handling of Enum's via Annotated, not direct class declaration.

This check in Method _removeEnumSelfReferences()
below did not count for having Enum having itself as a field.

if (propType.isEnumType() && propType.isTypeOrSubTypeOf(aClass)) {
.

@JooHyukKim
Copy link
Member

Filed a fix PR #4565 against 2.16 version.

@cowtowncoder
Copy link
Member Author

@JooHyukKim if you think this is safe enough against 2.16 that works (and 2.16 is still open until 2.18.0 release, as per https://github.com/FasterXML/jackson/wiki/Jackson-Releases).

I will try to get PR reviewed and merged soon.

@cowtowncoder cowtowncoder added this to the 2.16.3 milestone Jun 7, 2024
@cowtowncoder
Copy link
Member Author

Fixed in 2.16 (for potential 2.16.3 release) and onwards (2.17.2, 2.18.0).

@Mugiwara84
Copy link

Thank you !

@px100
Copy link

px100 commented Jul 5, 2024

@cowtowncoder when can we expect a release with this fix ?

@cowtowncoder
Copy link
Member Author

@px100 I am planning to release 2.17.2 today if all goes well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enum Related to handling of Enum values
Projects
None yet
Development

No branches or pull requests

5 participants