Search before asking

  • [x] I searched in the issues and found nothing similar.

Describe the bug

Since version 2.18.4 (probably since https://github.com/FasterXML/jackson-databind/issues/4628) behavior of annotation on methods of a records has changed.

before 2.18.4, If I had

    public record TestData(
            @JsonProperty("test_property")
            String value) {

        @JsonIgnore
        public Optional<String> getValue() {
            return Optional.ofNullable(value);
        }
    }

and serialized object

{ "test_property": "jackson" }

it would be deserialized to TestData("jackson")

Now since 2.18.4, the @JsonIgnore is applied to the value field even though getValue is not the record getter. This make TestData to be deserialized to TestData(null)

It seems that Jackson confuse the getValue() for the record getter value(). If I name my method something else than getX the @JsonIgnore is not applied to the field.

What make me think that its a bug is that an equivalent case with a class doesn't have the same behavior. If I have a class getter with @JsonIgnore the object mapper won't apply the ignore to the class field but only the method.

Version Information

2.18.4 and 2.19.0

Reproduction

package com.libon.jackson;

import static org.assertj.core.api.Assertions.assertThat;
import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;

class JacksonTest {

    private static final ObjectMapper MAPPER = new ObjectMapper().disable(FAIL_ON_UNKNOWN_PROPERTIES);

    @Test
    void should_deserialize_json_to_test_data() throws Exception {
        String json = """
                      {"test_property":"test value"}
                      """;

        var testData = MAPPER.readValue(json, JacksonTest.TestData.class);

        assertThat(testData.value()).isEqualTo("test value");
    }

    @Test
    void should_deserialize_json_to_test_data_class() throws Exception {
        String json = """
                      {"test_property":"test value"}
                      """;

        var testData = MAPPER.readValue(json, JacksonTest.TestDataClass.class);

        assertThat(testData.getValue()).contains("test value");
    }

    @Test
    void should_deserialize_json_to_test_data_alternate() throws Exception {
        String json = """
                      {"test_property":"test value"}
                      """;

        var testData = MAPPER.readValue(json, JacksonTest.TestDataAlternate.class);

        assertThat(testData.value()).isEqualTo("test value");
    }

    @Test
    void should_not_deserialize_wrong_json_model_to_test_data() throws Exception {
        String json = """
                      {"value":"test value"}
                      """;

        JacksonTest.TestData testData = MAPPER.readValue(json, JacksonTest.TestData.class);

        assertThat(testData.value()).isNull();
    }

    public record TestData(
            @JsonProperty("test_property")
            String value) {

        @JsonIgnore
        public Optional<String> getValue() {
            return Optional.ofNullable(value);
        }
    }

    public record TestDataAlternate(
            @JsonProperty("test_property")
            String value) {

        @JsonIgnore
        public Optional<String> optionalValue() {
            return Optional.ofNullable(value);
        }
    }

    public static final class TestDataClass {
        private final String value;

        public TestDataClass(
                @JsonProperty("test_property")
                String value) {
            this.value = value;
        }

        @JsonIgnore
        public Optional<String> getValue() {
            return Optional.ofNullable(value);
        }

    }
}

Expected behavior

The @JsonIgnore on a record method should not be applied to the record field especially when its not the field getter.

Additional context

No response

Comment From: JooHyukKim

Thank you for your report! I tried running tests provided and only should_deserialize_json_to_test_data fails. Is that what you're expecting @emouty ?

Comment From: JooHyukKim

Filed #5185 for reproduction

Comment From: emouty

Yes, the others are just here to demonstrate the different behaviours

Comment From: cowtowncoder

Right, the issue is that even though 2 accessors are related (getValue() is recognized as getter for property "value"; and Record field has "implicit" name of "value"), they are not considered a properly "split" case (one accessor to be ignore, other included).