Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ObjectMapper can't deserialize without default constructor after upgrade to Spring Boot 2

I have following DTOs:

@Value public class PracticeResults {     @NotNull     Map<Long, Boolean> wordAnswers; }  @Value public class ProfileMetaDto {      @NotEmpty     String name;     @Email     String email;     @Size(min = 5)     String password; } 

@Value is a Lombok annotation which generates a constructor. Which means that this class doesn't have a no-arg constructor.

I used Spring Boot 1.4.3.RELEASE and ObjectMapper bean was able to deserialize such object from JSON.

After the upgrade to Spring Boot 2.0.0.M7 I receive following exception:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of PracticeResults (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)

Jackson version used in Spring Boot 1.4.3 is 2.8.10 and for Spring Boot 2.0.0.M7 is 2.9.2.

I've tried to Google this problem but found only solutions with @JsonCreator or @JsonProperty.

So, why does it work with Spring Boot 1.4.3 and fails with Spring Boot 2? Is it possible to configure bean to behave the same way as the older version?

like image 673
solomkinmv Avatar asked Jan 18 '18 21:01

solomkinmv


2 Answers

Due to breaking changes in Lombok version 1.16.20 you need to set the following property in your lombok.config file (if you don't have this file you can create it in your project root):

lombok.anyConstructor.addConstructorProperties=true 

This is described in the Lombok changelog: https://projectlombok.org/changelog.

After that the @Value should be accepted again by Jackson.

You may be interested in following the related GitHub issue here, although it's about @Data: https://github.com/rzwitserloot/lombok/issues/1563

like image 132
Tobias Avatar answered Sep 19 '22 23:09

Tobias


One more way to solve this problem. Use Jackson parameter names module, which is included in spring boot 2 by default. After this Jackson can deserialize objects. But it works only if you have more than 1 property in object. In case of single property I receive following error message:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `SomeClassName` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator) 

Because of the following:

Marker annotation that can be used to define constructors and factory methods as one to use for instantiating new instances of the associated class.

NOTE: when annotating creator methods (constructors, factory methods), method must either be:

  • Single-argument constructor/factory method without JsonProperty annotation for the argument: if so, this is so-called "delegate creator", in which case Jackson first binds JSON into type of the argument, and then calls creator. This is often used in conjunction with JsonValue (used for serialization).
  • Constructor/factory method where every argument is annotated with either JsonProperty or JacksonInject, to indicate name of property to bind to

Also note that all JsonProperty annotations must specify actual name (NOT empty String for "default") unless you use one of extension modules that can detect parameter name; this because default JDK versions before 8 have not been able to store and/or retrieve parameter names from bytecode. But with JDK 8 (or using helper libraries such as Paranamer, or other JVM languages like Scala or Kotlin), specifying name is optional.

To handle this case with Lombok I've used following workaround:

@Value @AllArgsConstructor(onConstructor = @__(@JsonCreator(mode = JsonCreator.Mode.PROPERTIES))) class SomeClassName {...} 
like image 29
solomkinmv Avatar answered Sep 21 '22 23:09

solomkinmv