简体   繁体   中英

Jackson ignores @Ignore annotations

I am trying to let Jackson ignore some properties of a DTO, however I can't seem to be able to. I have a fairly big project with many dependencies (Lombok, Spring, GWT, Gemfire and many more), and I can't change these dependencies (maybe I can change versions, but it's not my call).

I have prepared a test case, here it is:

this is my test dto class, it has a map that is only useful server side. A copy of this dto is serialized to be sent to gwt to display (the implementation is not complete, only the relevant parts are shown).

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreType;
import lombok.*;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(of = "id", callSuper = true)
public class MyClass extends MyAbstractClass {

    @Getter
    @Setter
    @Builder
    public static class AValueClass {
        int someInt;
        String SomeString;
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @JsonIgnoreType
    public static class MyJsonIgnoreKeyClass {
        protected Integer anInt;
        protected String aString;
    }

    @JsonIgnore
    @Getter(AccessLevel.NONE) @Setter(AccessLevel.NONE)
    private transient Map<MyJsonIgnoreKeyClass, List<AValueClass>> aMapThatJacksonShouldIgnore = new HashMap<>();


    public void addToMap(MyJsonIgnoreKeyClass key, AValueClass value) {
        List<AValueClass> valueList = aMapThatJacksonShouldIgnore.get(key);
        if(valueList == null) {
           valueList = new ArrayList<>();
        }
        valueList.add(value);
        aMapThatJacksonShouldIgnore.put(key,valueList);
    }

    public boolean noMap() {
        return aMapThatJacksonShouldIgnore == null || aMapThatJacksonShouldIgnore.keySet().isEmpty();
    }

    public void nullifyMap() {
        aMapThatJacksonShouldIgnore = null;
    }

    // other methods operating on maps omitted
}

the test model inherits some fields from a superclass

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;

import java.util.Date;

@Setter
@Getter
@EqualsAndHashCode(of = "id")
public class MyAbstractClass {

    protected String id;
    protected Date aDay;
}

here are the unit tests I've prepared

public class MyClassJacksonTest {

    ObjectMapper om;

    @Before
    public void setUp() throws Exception {
        om = new ObjectMapper().registerModule(new Jdk8Module());
        om.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        om.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
    }

    @Test
    public void testWithMapValues() throws Exception {
        MyClass testClass = new MyClass();
        testClass.setADay(new Date());
        testClass.setId(UUID.randomUUID().toString());
        testClass.addToMap(
                new MyClass.MyJsonIgnoreKeyClass(1,"test"),
                new MyClass.AValueClass(1,"test"));

        StringWriter writer = new StringWriter();
        om.writeValue(writer,testClass);
        writer.flush();
        String there = writer.toString();
        MyClass andBackAgain = om.readValue(there, MyClass.class);

        assertTrue(andBackAgain.noMap());
    }

    @Test
    public void testWithEmptyMaps() throws Exception {
        MyClass testClass = new MyClass();
        testClass.setADay(new Date());
        testClass.setId(UUID.randomUUID().toString());

        StringWriter writer = new StringWriter();
        om.writeValue(writer,testClass);
        writer.flush();
        String there = writer.toString();
        MyClass andBackAgain = om.readValue(there, MyClass.class);

        assertTrue(andBackAgain.noMap());
    }

    @Test
    public void testWithNullMaps() throws Exception {
        MyClass testClass = new MyClass();
        testClass.setADay(new Date());
        testClass.setId(UUID.randomUUID().toString());
        testClass.nullifyMap();

        StringWriter writer = new StringWriter();
        om.writeValue(writer,testClass);
        writer.flush();
        String there = writer.toString();
        MyClass andBackAgain = om.readValue(there, MyClass.class);

        assertTrue(andBackAgain.noMap());
    }

}

All of the tests are failing with

com.fasterxml.jackson.databind.JsonMappingException: Can not find a (Map) Key deserializer for type [simple type, class MyClass$MyJsonIgnoreKeyClass]

So the questions are : Why Jackson tries to find a deserializer for the keys of a map that can't be accessed (since there are no getter and setter) and that is annotated with @JsonIgnore ? More importantly, how can I tell it not to search for the deserializers?

These are the relevant dependencies on my pom, if it can be of any help :

<properties>
    <!-- ... -->
    <jackson.version>2.7.4</jackson.version>
</properties>

<dependencies>
  <!-- other dependencies omitted -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>${jackson.version}</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.module</groupId>
        <artifactId>jackson-module-jsonSchema</artifactId>
        <version>${jackson.version}</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.datatype</groupId>
        <artifactId>jackson-datatype-jdk8</artifactId>
        <version>${jackson.version}</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.datatype</groupId>
        <artifactId>jackson-datatype-jsr353</artifactId>
        <version>${jackson.version}</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.dataformat</groupId>
        <artifactId>jackson-dataformat-xml</artifactId>
        <version>${jackson.version}</version>
        <exclusions>
            <exclusion>
                <groupId>org.codehaus.woodstox</groupId>
                <artifactId>stax2-api</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

It turns out that this is a case of bad interaction between Lombok and Jackson. The Lombok annotation @AllArgsConstructor generates a constructor that is annotated with @ConstructorProperties , which in turn lists all the properties that are declared in the class. This is then used by Jackson when the default deserializer is to be used. In this case, the absence of setters and getters and the presence of @JsonIgnore annotations is not taken into account.

The solution is simply to specify the @AllArgsConstructor with the attribute suppressConstructorProperties set to true :

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor(suppressConstructorProperties = true)
@EqualsAndHashCode(of = "id", callSuper = true)
public class MyClass extends MyAbstractClass {
  // everything else is unchanged

Tricky one, indeed. What I think is happening is that you are generating a public all arguments constructor with Lombok. When deserializing, that is the one that Jackson will try to use. If you change your annotation for MyClass to

@AllArgsConstructor(access = AccessLevel.PRIVATE)

... it should work fine. Good luck!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM