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.