Jackson causing duplicate field serialization in Spring Boot application
Description:
I have encountered an issue while using Jackson for JSON serialization in my Spring Boot application. Despite having only one field annotated with a custom serializer, the response JSON contains two fields with the same value but different case sensitivity.
Expected Behavior:
The expected JSON response should contain only one field for the pId property:
json
{
"pId": "123"
}
Actual Behavior:
The actual JSON response contains two fields, pId and pid, both with the same value:
json
{
"pId": "123",
"pid": "123"
}
Steps to Reproduce:
Define a class MaksData with a single field pId annotated with a custom annotation @DataCode.
Create a custom annotation @DataCode that is meta-annotated with @JacksonAnnotationsInside and @JsonSerialize using DataCodeHandler as the serializer.
Implement DataCodeHandler as a custom JsonSerializer for handling the serialization of the annotated field.
Define a REST controller TestController with a single endpoint /test that returns an instance of MaksData.
Start the Spring Boot application and access the /test endpoint.
Code Snippets:
(Attach the provided code snippets for MaksData, @DataCode, DataCodeHandler, TestController, and the relevant parts of the pom.xml file.) demo.zip
(domain.java)
public class MaksData {
@DataCode
private String pId;
public String getPId() {
return pId;
}
public void setPId(String pId) {
this.pId = pId;
}
}
(DataCode.java)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = DataCodeHandler.class)
public @interface DataCode {
}
(DataCodeHandler.java)
public class DataCodeHandler extends JsonSerializer<String> implements ContextualSerializer {
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider prov) throws IOException {
gen.writeString(value);
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty prop) throws JsonMappingException {
return prov.findValueSerializer(prop.getType(), prop);
}
}
(TestController.java)
@RestController
@RequestMapping("/")
public class TestController {
@GetMapping("/test")
public MaksData test() {
MaksData maksData = new MaksData();
maksData.setPId("123");
return maksData;
}
}
(DemoApplication.java)
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
(pom.xml)
<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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
Environment Details:
Spring Boot Version: 3.2.1 Java Version: 21 Jackson Version: 2.15.3
Comment From: philwebb
I think that you're seeing standard Jackson behavior what isn't related to Spring Boot. When serializing MaksData Jackson is using field serialization for private String pId because of the @JsonSerialize meta-annotation but it's also using method serialization for public String getPId().
If you add @JsonIgnore to the getPId() method the duplicate field goes away.