jackson-databind
jackson-databind copied to clipboard
Problem with property name on deserialization wrt non-standard property names
(related to earlier issue #3538)
Describe the bug
If the field name is pId
, the JSON data is not mapped to an object using @RequestBody
annotation.
Lombok's @Getter
, @Setter
annotation, the naming becomes getPId()
, setPId()
, but if the getter, setter of IDE( IntelliJ), it becomes getpId()
, setpId()
It seems to be an issue caused by the change in the basename returned according to the name of the getter/setter in the egacyManglePropertyName(final String basename, final int offset) method of the DefaultAccessorNamingStrategy class. (I think it's caused by different naming rules in JavaBeans, Lombok, Jackson)
DefaultAccessorNamingStrategy class {
...
protected String legacyManglePropertyName(final String basename, final int offset)
{
final int end = basename.length();
if (end == offset) { // empty name, nope
return null;
}
char c = basename.charAt(offset);
// 12-Oct-2020, tatu: Additional configurability; allow checking that
// base name is acceptable (currently just by checking first character)
if (_baseNameValidator != null) {
if (!_baseNameValidator.accept(c, basename, offset)) {
return null;
}
}
// next check: is the first character upper case? If not, return as is
char d = Character.toLowerCase(c);
if (c == d) {
return basename.substring(offset);
}
// otherwise, lower case initial chars. Common case first, just one char
StringBuilder sb = new StringBuilder(end - offset);
sb.append(d);
int i = offset+1;
for (; i < end; ++i) {
c = basename.charAt(i);
d = Character.toLowerCase(c);
if (c == d) {
sb.append(basename, i, end);
break;
}
sb.append(d);
}
return sb.toString();
}
}
filed name: private String pId;
case 1. using Lombok : setPId()
case 2. using setter method : setpId()
As in the comments on the #3538 issue, the problem is solved by not using Lombok, changing the field naming, or using @JsonProperty
However, as you may know, many developers are using Lombok to remove the boilerplate code, and as the #3538 last cowtowncoder said, I wonder if you have any plans to improve this.
Version Information
2.12.3 (maybe others)
Reproduction
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@PostMapping("/api/v1/test")
public void test(@RequestBody TestBody body) {
System.out.println(body.getPId()); // Lombok -> null
System.out.println(body.getpId()); // getter -> success binding
}
@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class TestBody {
private String pId; // jackson to pid using Lombok getter (getPId())
private String poId; // jackson to poId
public String pId() {
return pId;
}
}
}
### Send POST request with json body
POST http://localhost:8080/api/v1/test
Content-Type: application/json
{
"pId": "pId",
"poId": "poId"
}
Expected behavior
No response
Additional context
No response
We need a test reproduction that does not have 3rd party dependencies: here we at least 2 (Lombok, some REST framework (Spring?)) to show what change would be suggested, without external dependencies (use case may be to support such frameworks but Jackson itself has no and should not have dependency to f.ex Lombok).
I'll also change title to remove reference to another issue (it belongs in description not titla)
@cowtowncoder this is reproduction code :)
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class JacksonBindTest {
@Test
void exception() {
ObjectMapper objectMapper = new ObjectMapper();
JavaType javaType = objectMapper.constructType(new TypeReference<FailWithLombokRequest>() {
});
final String json = "{\"pId\": \"pId\", \"poId\": \"poId\"}";
assertThrows(UnrecognizedPropertyException.class, () -> objectMapper.readValue(new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)), javaType));
}
@Test
void success() throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
JavaType javaType = objectMapper.constructType(new TypeReference<SuccessRequest>() {
});
final String json = "{\"pId\": \"pId\", \"poId\": \"poId\"}";
final SuccessRequest failRequest = objectMapper.readValue(new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)), javaType);
assertThat(failRequest.getpId()).isEqualTo("pId");
}
public static class FailWithLombokRequest {
private String pId;
private String poId;
public String getPId() {
return pId;
}
public void setPId(String pId) {
this.pId = pId;
}
public String getPoId() {
return poId;
}
public void setPoId(String poId) {
this.poId = poId;
}
}
public static class SuccessRequest {
private String pId; // jackson to pid
private String poId; // jackson to poId
public String getpId() { // this is different from FailRequest
return pId;
}
public void setpId(String pId) { // this is different from FailRequest
this.pId = pId;
}
public String getPoId() {
return poId;
}
public void setPoId(String poId) {
this.poId = poId;
}
}
}