Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lombok 1.18.0 and Jackson 2.9.6 not working together

The deserialization is failing after the update.

I updated my micro-service from Spring 1.5.10.RELEASE to Spring 2.0.3.RELEASE and also updated the lombok from 1.16.14 to 1.18.0 and jackson-datatype-jsr310 from 2.9.4 to 2.9.6.

The JSON string -

{"heading":"Validation failed","detail":"field must not be null"} 

The Class -

@Data @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public class ErrorDetail {     private final String heading;    private final String detail;    private String type; } 

Method call -

ErrorDetail errorDetail = asObject(jsonString, ErrorDetail.class); 

The method used to deserialize -

import com.fasterxml.jackson.databind.ObjectMapper; // more imports and class defination.  private static <T> T asObject(final String str, Class<T> clazz) {     try {         return new ObjectMapper().readValue(str, clazz);     } catch (Exception e) {         throw new RuntimeException(e);     } } 

Error -

java.lang.RuntimeException: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.foo.bar.ErrorDetail` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator) at [Source: (String)"{"heading":"Validation failed","detail":"field must not be null"}"; line: 1, column: 2] 
like image 865
JHS Avatar asked Jul 22 '18 11:07

JHS


People also ask

Does Lombok work with Jackson?

Lombok @Jacksonized. Using @Jacksonized annotation is the simplest solution if you are using Jackson for deserialization purposes. Just annotate the @Builder class with @Jacksonized annotation and we are good for converting the JSON string to Java object.

Does Jackson need default constructor?

Jackson uses default (no argument) constructor to create object and then sets value using setters. so you only need @NoArgsConstructor and @Setter.

Why Lombok should not be used?

The downside is it clutters your source with boilerplate code that take the focus off the important stuff. After 5 months, we're still using Lombok, but I have some other annoyances. The lack of a declared getter & setter can get annoying at times when you are trying to familiarize yourself with new code.

What is JsonPOJOBuilder?

@JsonPOJOBuilder The @JsonPOJOBuilder annotation is used to configure a builder class to customize deserialization of a JSON document to recover POJOs when the naming convention is different from the default.

How to make Jackson and Lombok work well together?

The best way to make jackson and lombok play well together is to always make your DTO's immutable, and tell jackson to use the builder to deserialize into your objects. Immutable objects are good idea for the simple reason that when fields cannot be modified in situ compilers can do much more aggressive optimisations.

How to add annotations to constructor parameters in Lombok?

Since lombok 1.18.4, you can configure what annotations are copied to the constructor parameters. Insert this into your lombok.config: ... You'll need a @JsonProperty on every field even if the name matches, but that is a good practice to follow anyway.

What happened to @constructorproperties in @Lombok?

Lombok stopped generating @ConstructorProperties on constructors with version 1.16.20 (see changelog ), because it might break Java 9+ applications that use modules.

Why can't Jackson map the field names to the constructor parameters?

That annotation contains the names of the constructor's parameters (they are removed when compiling the class, so that's a workaround so that the parameter names still can be retrieved at runtime). Because the annotation is now not being generated by default, Jackson cannot map the field names to the constructor parameters.


2 Answers

Lombok stopped generating @ConstructorProperties on constructors with version 1.16.20 (see changelog), because it might break Java 9+ applications that use modules. That annotation contains the names of the constructor's parameters (they are removed when compiling the class, so that's a workaround so that the parameter names still can be retrieved at runtime). Because the annotation is now not being generated by default, Jackson cannot map the field names to the constructor parameters.

Solution 1: Use a @NoArgsConstructor and @Setter, but you will loose immutability (if that's important to you).

Update: Just @NoArgsConstructor and @Getter (without @Setter) may also work (because INFER_PROPERTY_MUTATORS=true). In this way, you can keep the class immutable, at least from regular (non-reflective) code.

Solution 2: Configure lombok to generate the annotations again, using a lombok.config file containing the line lombok.anyConstructor.addConstructorProperties = true. (If you are using modules, make sure java.desktop is on your module path.) Clean and recompile after you added the lombok.config file.

Solution 3: Use Jackson's builder support in combination with lombok's (@Jacksonized) @Builder/@SuperBuilder, as described in @Randakar answer to this question.

Solution 4: When compiling with javac (of Java 8 and above), append -parameters to the command. This will store the parameter names of constructors and methods in the generated class files, so they can be retrieved via reflection.

like image 64
Jan Rieke Avatar answered Oct 09 '22 04:10

Jan Rieke


Edit: This answer is now a bit outdated: There is a new @Jacksonized annotation, from https://projectlombok.org/features/experimental/Jacksonized, which takes care of much of the boilerplate in this answer.


The best way to make jackson and lombok play well together is to always make your DTO's immutable, and tell jackson to use the builder to deserialize into your objects.

Immutable objects are good idea for the simple reason that when fields cannot be modified in situ compilers can do much more aggressive optimisations.

In order to do this you need two annotations: JsonDeserialize, and JsonPojoBuilder.

Example:

@Builder @Value // instead of @Data @RequiredArgsConstructor @NonNull // Best practice, see below. @JsonDeserialize(builder = ErrorDetail.ErrorDetailBuilder.class) @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public class ErrorDetail {     private final String heading;     // Set defaults if fields can be missing, like this:    @Builder.Default    private final String detail = "default detail";     // Example of how to do optional fields, you will need to configure    // your object mapper to support that and include the JDK 8 module in your dependencies..    @Builder.Default    private Optional<String> type = Optional.empty()     @JsonPOJOBuilder(withPrefix = "")    public static final class ErrorDetailBuilder {    } } 
like image 21
Randakar Avatar answered Oct 09 '22 05:10

Randakar