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

Problem with property name on deserialization wrt non-standard property names #4421

Open
JuHyun419 opened this issue Mar 8, 2024 · 2 comments
Labels
need-test-case To work on issue, a reproduction (ideally unit test) needed

Comments

@JuHyun419
Copy link

JuHyun419 commented Mar 8, 2024

(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()
image


case 2. using setter method : setpId()
image

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"
}

image

Expected behavior

No response

Additional context

No response

@JuHyun419 JuHyun419 added the to-evaluate Issue that has been received but not yet evaluated label Mar 8, 2024
@cowtowncoder cowtowncoder added need-test-case To work on issue, a reproduction (ideally unit test) needed and removed to-evaluate Issue that has been received but not yet evaluated labels Mar 8, 2024
@cowtowncoder
Copy link
Member

cowtowncoder commented Mar 8, 2024

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 cowtowncoder changed the title regarding the issue #3538 (field name changed when deserializing bean) Problem with property name on deserialization wrt non-standard property names Mar 8, 2024
@JuHyun419
Copy link
Author

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

}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
need-test-case To work on issue, a reproduction (ideally unit test) needed
Projects
None yet
Development

No branches or pull requests

2 participants