Lombok: [BUG] Jackson annotations are not copied to builder methods if Singular is present

Created on 26 Mar 2020  路  1Comment  路  Source: projectlombok/lombok

Describe the bug
Currently Jackson annotations (in my case @JsonProperty) is only copied properly on Builder methods if the Collection is not annotated with @Singular. This makes deserialization with Jackson completely reliant on having the field named the same way it will be in the JSON file with no additional flexibility.

To Reproduce
Here is an example of the source code with and without the Singular annotation vs the decompiled classes.

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import lombok.Builder;
import lombok.Singular;

import java.util.Collection;

@Builder
@JsonDeserialize(builder = SomePojo.SomePojoBuilder.class)
public class SomePojo {

    @JsonProperty("foos")
    private Collection<String> notFoos;

    @Singular
    @JsonProperty("bars")
    private Collection<String> notBars;

    @JsonPOJOBuilder(withPrefix = "")
    public static class SomePojoBuilder {

    }
}
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

@JsonDeserialize(
    builder = SomePojo.SomePojoBuilder.class
)
public class SomePojo {
    @JsonProperty("foos")
    private Collection<String> notFoos;
    @JsonProperty("bars")
    private Collection<String> notBars;

    SomePojo(@JsonProperty("foos") Collection<String> notFoos, @JsonProperty("bars") Collection<String> notBars) {
        this.notFoos = notFoos;
        this.notBars = notBars;
    }

    public static SomePojo.SomePojoBuilder builder() {
        return new SomePojo.SomePojoBuilder();
    }

    @JsonPOJOBuilder(
        withPrefix = ""
    )
    public static class SomePojoBuilder {
        private Collection<String> notFoos;
        private ArrayList<String> notBars;

        SomePojoBuilder() {
        }

        @JsonProperty("foos")
        public SomePojo.SomePojoBuilder notFoos(@JsonProperty("foos") Collection<String> notFoos) {
            this.notFoos = notFoos;
            return this;
        }

        public SomePojo.SomePojoBuilder notBar(String notBar) {
            if (this.notBars == null) {
                this.notBars = new ArrayList();
            }

            this.notBars.add(notBar);
            return this;
        }

        public SomePojo.SomePojoBuilder notBars(Collection<? extends String> notBars) {
            if (this.notBars == null) {
                this.notBars = new ArrayList();
            }

            this.notBars.addAll(notBars);
            return this;
        }

        public SomePojo.SomePojoBuilder clearNotBars() {
            if (this.notBars != null) {
                this.notBars.clear();
            }

            return this;
        }

        public SomePojo build() {
            List notBars;
            switch(this.notBars == null ? 0 : this.notBars.size()) {
            case 0:
                notBars = Collections.emptyList();
                break;
            case 1:
                notBars = Collections.singletonList((String)this.notBars.get(0));
                break;
            default:
                notBars = Collections.unmodifiableList(new ArrayList(this.notBars));
            }

            return new SomePojo(this.notFoos, notBars);
        }

        public String toString() {
            return "SomePojo.SomePojoBuilder(notFoos=" + this.notFoos + ", notBars=" + this.notBars + ")";
        }
    }
}

Here is a test case to further illustrate the difference in behavior between Singular vs . non-Singular

import static org.assertj.core.api.Assertions.assertThat;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;

import java.util.Collections;

public class SomePojoTest {

    @Test
    public void testDeserialization() throws JsonProcessingException {

        final ObjectMapper mapper =  new ObjectMapper();
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        final SomePojo expected = SomePojo.builder().notFoos(Collections.singleton("Hello")).notBar(("World!")).build();

        final SomePojo actual = mapper.readValue("{\"foos\":[\"Hello\"],\"bars\":[\"World!\"]}", SomePojo.class);

        assertThat(actual).usingRecursiveComparison().isEqualTo(expected);
    }
}

The assertion fails as follows:

field/property 'notBars' differ:
- actual value   : []
- expected value : ["World!"]
actual and expected values are collections of different size, actual size=0 when expected size=1

My lombok.config

config.stopBubbling = true

lombok.copyableAnnotations += com.fasterxml.jackson.annotation.JsonProperty

Expected behavior
Inside the builder the notBars(Collection<? extends String> notBars) should receive a copy of the @JsonProperty annotations, so that the aforementioned test case can pass without renaming the field notBars to bars (which means @JsonProperty is redundant)

Version info (please complete the following information):

  • Lombok version 1.18.12
  • Platform IntelliJ IDEA 2019.3.2 (Ultimate Edition)

Most helpful comment

Fixed with PR #2429. Will work out of the box, so no config settings required.

>All comments

Fixed with PR #2429. Will work out of the box, so no config settings required.

Was this page helpful?
0 / 5 - 0 ratings