Testing validation of bean with lots of properties (and child objects) - java

Using Springboot 2.5.7 with bundled Junit5 (via spring-boot-starter-test), I'm trying to test the constraints that I have put in place all over the bean via custom an standard annotations.
Most of the documentation I found was about junit4 and I cannot find a way to make it work in springboot's junit5.
Also, I'm a bit puzzled because, ideally, I would like to test contraint per constraint and only found documentation on how to test it globally (using junit4).
Does anyone already fiddled with that kind of testing?
Just for the sample, here I have a bean for my inbound SQS event:
package com.application.sqs.dto.inbound;
import com.application.sqs.dto.validators.ZoneDateTime;
import lombok.Data;
import java.util.List;
#Data
public class SqsEvent {
private String version;
private String id;
private String source;
private String account;
#ZoneDateTime(
message = "Wrong time."
)
private String time;
private String region;
private List<String> resources;
private SqsPayload detail;
}
And an example of a payload (that has lots of child objects, edited):
package com.application.sqs.dto.inbound;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.application.sqs.dto.validators.LocalDate;
import lombok.Data;
import javax.validation.constraints.Digits;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import java.util.List;
#Data
#JsonIgnoreProperties(ignoreUnknown = true)
public class SqsPayload {
#JsonProperty("primkey")
#NotBlank(
message = "primkey is blank"
)
private String primarykey;
#JsonProperty("numb")
#Pattern(
regexp="^(A|B)$",
message = "numb is invalid"
)
private String number;
}
I'd like to test that numb only accepts A or B, that primkey must not be blank and that my custom validator (#ZonedDateTime) is working as intended for example.
Thanks a lot for your help and guidance!
Best,

Haven't done per individual constraint, but below are 2 possibilities.
Option 1
If you run an instance of that class through the javax validator, then you will get one ConstraintViolation per invalid constraint. That way, you could just validate that you get all expected ConstaintViolation's for each constraint.
Option 2
define a valid mock object, and then in each test case, you just change a value of a constrained field into an invalid case, and ensure that it catches it
Details on programmatic javax validation
For programmatic javax validation, you could have a look here on step 5.
Also suggest you have a deeper look at the javax.validation.Validator interface on available methods. You also have a method called validateProperty to target specific properties as well.

Related

Why doesn't spring framework bind my YAML list to an entity?

I'm trying to bind a list of objects to my entity class using #ConfigurationProperties annotation. Spring Boot framework seems to ignore that annotation, and it literally does nothing.
Here is my application.yml properties file:
spring:
datasource:
url: jdbc:mysql://localhost:3306/search-engine
username: landsreyk
password: 12345678
jpa:
database-platform: org.hibernate.dialect.MySQLDialect
show-sql: false
hibernate:
ddl-auto: none
sites:
- url: http://someurl1.com
name: somename1
- url: https://someurl2.com
name: somename2
- url: https://someurl3.com
name: somename3
And here is my entity class:
package main.model;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import javax.persistence.*;
import java.sql.Timestamp;
#Getter
#Setter
#ToString
#Entity
#Table(name = "_site")
public class Site {
#Column(nullable = false)
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Enumerated(EnumType.STRING)
#Column(name = "status")
private Status status;
#Column(name = "status_time")
private Timestamp statusTime;
#Column(name = "last_error")
private String lastError;
private String url;
private String name;
public enum Status {
INDEXING, INDEXED, FAILED
}
}
Binding is pretty straight forward:
package main.utilities;
import lombok.Getter;
import lombok.Setter;
import main.model.Site;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
#ConfigurationProperties
public class ApplicationProperties {
#Getter
#Setter
private List<Site> sites;
}
Somewhere in application I'm testing that binding, let's say I created end point in my API to test that binding, i.e. my controller calls a method which just prints all objects in a list:
package main.application.indexer;
import main.utilities.ApplicationProperties;
import org.springframework.beans.factory.annotation.Autowired;
public class IndexBuilder {
#Autowired
private ApplicationProperties properties;
public void run() {
System.out.println(properties.getSites());
}
}
Expected:
After launch, ApplicationProperties instance is not null. Calling properties.getSites() returns a list of Site objects. Each Site object has url and name fields initialized from yaml source.
Actual:
After launch ApplicationProperties instance is null.
I was shocked to realize that Spring wasn't able to accomplish such a simple binding.
Knowing that just parsing a yaml file is not such a hard task to accomplish, I thought that Spring Framework should have this feature build-in.
How do I bind my list?
By the way, here is my project structure.
UPDATE:
I edited IndexBuilder class. Added #Configuration and #Bean annotations.
Now it looks like this:
package main.application.indexer;
import main.utilities.ApplicationProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class IndexBuilder {
#Autowired
private ApplicationProperties properties;
#Bean
public void run() {
System.out.println(properties.getSites());
}
}
That fixed the problem with initialization, but Spring Framework just call run() immideately after launch. That isn't an expected behavior.
UPDATE 2:
Answer of this user is completely authentic.
He is right.
Instead of using #Configuration you should use #Component on your IndexBuilder class.
#Configuration should only be used for configuration classes that define the application‘s beans. Here it is actually expected that the methods annotated with #Bean are executed immediately on application start. This is the phase where spring creates all needed beans. However I’m wondering spring accepts a void method with the #Bean annotation.
What’s your expected time when the run method should be executed? With a normal bean/component/service you could for example use the #PostConstruct annotation on methods. These will however be executed in the very same phase as the #Bean methods or at least not much later.
Turns out, Spring isn't that smart as I thought.
To access #ConfigurationProperties class, that is: accessing a bean from a non-managed-by-spring class, - I had to go through this article:
Autowiring Spring Beans Into Classes Not Managed by Spring
https://dzone.com/articles/autowiring-spring-beans-into-classes-not-managed-by-spring
It solved the problem.

Not able to access application.yml properties in springboot component

I have an application.yml file which contains properties as below
NAME:
CLASS:
ID: ABC123456
and here is my spring boot component class
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
#Component
#Slf4j
public class ProcessMe {
#Value("${NAME.CLASS.ID}")
String StuId;
public boolean IsRightOrWrong(){
System.out.println(StuId);
}
}
In above componenent System.out.println(StuId); is always coming as null. When i am tying to call this function using Junit test class. What's wrong in the above code?
I suppose this solution to define in constructor like
#Component
#Slf4j
public class ProcessMe {
String StuId;
#Autowired
ProcessMe(#Value("${NAME.CLASS.ID}") String StuId) {
this.StuId = StuId;
}
public boolean IsRightOrWrong(){
System.out.println(this.StuId);
}
}
Hope useful
If you can start the application then I believe that your configuration is correct. I mean that StuId have some value.
The problem is how you testing When i am tying to call this function using Junit test class. What's wrong in the above code?
It seems that you're calling from the unit test. Then you need to clone application.yaml to test profile then your test container can read data, otherwise you have to mock your configuration.
btw: Java Object property should using camel case lol

A null pointer exception of connecting hybris database using flexiblesearch service because of getJaloResult() error

A null pointer exception occurred when trying to connect to hybris database using hybris flexible search service seemingly due to getJaloResult() method.
I need to retrieve certain data from hybris commerce database. I tried to use hybris flexible service to do that by using defaultFlexibleSearchService.search() method, but I got a null pointer exception. It seems that the exception occurred when search() method tries to call getJaloResult() method. I have no clue about the solution - thanks for any hints.
My class definition code is here
package de.hybris.platform.integrationservices.audit;
import java.util.stream.Stream;
import com.sun.tools.javac.util.List;
import de.hybris.platform.audit.TypeAuditReportConfig;
import de.hybris.platform.audit.view.AuditViewService;
import de.hybris.platform.audit.view.impl.ReportView;
import de.hybris.platform.servicelayer.search.FlexibleSearchQuery;
import de.hybris.platform.servicelayer.search.FlexibleSearchService;
import de.hybris.platform.servicelayer.search.RelationQuery;
import de.hybris.platform.servicelayer.search.SearchResult;
import de.hybris.platform.servicelayer.search.impl.DefaultFlexibleSearchService;
import de.hybris.platform.integrationservices.model.IntegrationObjectModel;
public class IntegrationObjectAudit implements AuditViewService
{
private DefaultFlexibleSearchService defaultFlexibleSearchService;
public IntegrationObjectAudit() {
defaultFlexibleSearchService = new DefaultFlexibleSearchService();
}
public List<IntegrationObjectModel> searchModel(){
String query = "SELECT {PK} FROM {IntegrationObject}";
FlexibleSearchQuery flexibleSearchQuery = new FlexibleSearchQuery(query);
flexibleSearchQuery.setCount(1);
de.hybris.platform.servicelayer.search.SearchResult<IntegrationObjectModel> resListIntegrationModel = this.defaultFlexibleSearchService.search(query);
List<IntegrationObjectModel> resList = (List<IntegrationObjectModel>) resListIntegrationModel.getResult();
return resList;
}
}
Didnt notice it was posted days back. Hope you already resolved it. Still adding my answer as it may be helpful for others.
What I see you are not injecting flexibleSearchService bean in IntegrationObjectAudit. And as flexibleSearchService bean is not injected, its causing NullPointerException when you call any function on it.
You can fix it as following (spring bean injection concept)
Either you should create a setter function and inject it by spring xml
Or use #Resource or #Autowire annotation for same
public class IntegrationObjectAudit implements AuditViewService
{
#Resource
private DefaultFlexibleSearchService flexibleSearchService;
.....
Hope it helps. Let me know please.

Spring Boot class Size not found #Size(min=1,max=18)

I have a User class as below and I am trying to enforce validation and I am getting an:
error cannot find symbol symbol: class Size
My class looks something like this:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
#Entity
public class User {
#Id
private long id;
#Size(min=1,max=18)
private int age;
// getters and setters
}
You are missing an import for the size annotation.
import javax.validation.constraints.Size;
Most people are doing the quick answer which is "did you import" but I am guessing you are a semi-intelligent developer and would understand how to import a class. My guess is that the class isn't on the classpath and as such your IDE can't figure out how to import the annotation. This is because the annotation is part of J6EE which isn't in the JDK. They have made the validation annotations their own library called validation-api. It is currently at version 1.1.0. To make it easier than using that library you should really look at using the hibernate-validator library as it will include that api library along with the proper implemented classes to actually validate beans.
Example of how to do validation is in a Getting Started guide.

Can't make Jackson and Lombok work together

I am experimenting in combining Jackson and Lombok. Those are my classes:
package testelombok;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.experimental.Wither;
#Value
#Wither
#AllArgsConstructor(onConstructor=#__(#JsonCreator))
public class TestFoo {
#JsonProperty("xoom")
private String x;
private int z;
}
package testelombok;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xebia.jacksonlombok.JacksonLombokAnnotationIntrospector;
import java.io.IOException;
public class TestLombok {
public static void main(String[] args) throws IOException {
TestFoo tf = new TestFoo("a", 5);
System.out.println(tf.withX("b"));
ObjectMapper om = new ObjectMapper().setAnnotationIntrospector(new JacksonLombokAnnotationIntrospector());
System.out.println(om.writeValueAsString(tf));
TestFoo tf2 = om.readValue(om.writeValueAsString(tf), TestFoo.class);
System.out.println(tf2);
}
}
Those are the JARs that I'm adding into the classpth:
Lombok: https://projectlombok.org/downloads/lombok.jar (version 1.16.10)
Jackson annotations: http://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.8.2/jackson-annotations-2.8.2.jar
Jackson core: http://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.8.2/jackson-core-2.8.2.jar
Jackson databind: http://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.8.2/jackson-databind-2.8.2.jar
Jackson-lombok: http://repo1.maven.org/maven2/io/paradoxical/jackson-lombok/1.1/jackson-lombok-1.1.jar
I am compiling it with Netbeans (I don't think that this is really relevant, but I am reporting this anyway to make it perfectly and faithfully reproducible). The five JARs above are kept in a folder called "lib" inside the project folder (along with "src", "nbproject", "test" and "build"). I added them to Netbeans via the "Add JAR/Folder" button in the project properties and they are listed in the exact order as the list above. The project is a standard "Java application" type project.
Further, the Netbeans project is configured to "do NOT compile on save", "generate debugging info", "report deprecated APIs", "track java dependencies", "activacte annotation proccessing" and "activacte annotation proccessing in the editor". No annotation processor or annotation processing option is explicitly configured in Netbeans. Also, the "-Xlint:all" command line option is passed in the compiler command line, and the compiler runs on an external VM.
My javac's version is 1.8.0_72 and my java's version is 1.8.0_72-b15. My Netbeans is 8.1.
My project compiles fine. However, it throws an exception in its execution. The exception don't seems to be anything that looks easily or obvious fixable. Here is the output, including the stacktrace:
TestFoo(x=b, z=5)
{"z":5,"xoom":"a"}
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Argument #0 of constructor [constructor for testelombok.TestFoo, annotations: {interface java.beans.ConstructorProperties=#java.beans.ConstructorProperties(value=[x, z]), interface com.fasterxml.jackson.annotation.JsonCreator=#com.fasterxml.jackson.annotation.JsonCreator(mode=DEFAULT)}] has no property name annotation; must have name when multiple-parameter constructor annotated as Creator
at [Source: {"z":5,"xoom":"a"}; line: 1, column: 1]
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:296)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:269)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(DeserializerCache.java:244)
at com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(DeserializerCache.java:142)
at com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(DeserializationContext.java:475)
at com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(ObjectMapper.java:3890)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3785)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2833)
at testelombok.TestLombok.main(TestLombok.java:14)
Caused by: java.lang.IllegalArgumentException: Argument #0 of constructor [constructor for testelombok.TestFoo, annotations: {interface java.beans.ConstructorProperties=#java.beans.ConstructorProperties(value=[x, z]), interface com.fasterxml.jackson.annotation.JsonCreator=#com.fasterxml.jackson.annotation.JsonCreator(mode=DEFAULT)}] has no property name annotation; must have name when multiple-parameter constructor annotated as Creator
at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory._addDeserializerConstructors(BasicDeserializerFactory.java:511)
at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory._constructDefaultValueInstantiator(BasicDeserializerFactory.java:323)
at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory.findValueInstantiator(BasicDeserializerFactory.java:253)
at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.buildBeanDeserializer(BeanDeserializerFactory.java:219)
at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.createBeanDeserializer(BeanDeserializerFactory.java:141)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer2(DeserializerCache.java:406)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer(DeserializerCache.java:352)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:264)
... 7 more
I already tried about randomly poking with the #Value and #AllArgsConstructor annotations, but I couldn't make it any better.
I google'd the exception and found an old bug report on jackson, and another one that is open, but seems to be related to something else. However, this still do not tells anything about what is this bug or how to fix it. Also, I could not find anything useful looking that somewhere else.
Since what I am trying to do is very basic usage of both lombok and jackson, it seems odd that I couldn't find any more useful information about how to workaround this issue. Maybe I missed something?
Other than just saying "don't use lombok" or "don't use jackson", do anybody has any idea about how to solve this?
If you want immutable but a json serializable POJO using lombok and jackson.
Use jacksons new annotation on your lomboks builder #JsonPOJOBuilder(withPrefix = "")
I tried this solution and it works very well.
Sample usage
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import lombok.Builder;
import lombok.Value;
#JsonDeserialize(builder = Detail.DetailBuilder.class)
#Value
#Builder
public class Detail {
private String url;
private String userName;
private String password;
private String scope;
#JsonPOJOBuilder(withPrefix = "")
public static class DetailBuilder {
}
}
If you have too many classes with #Builder and you want don't want the boilerplate code empty annotation you can override the annotation interceptor to have empty withPrefix
mapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {
#Override
public JsonPOJOBuilder.Value findPOJOBuilderConfig(AnnotatedClass ac) {
if (ac.hasAnnotation(JsonPOJOBuilder.class)) {//If no annotation present use default as empty prefix
return super.findPOJOBuilderConfig(ac);
}
return new JsonPOJOBuilder.Value("build", "");
}
});
And you can remove the empty builder class with #JsonPOJOBuilder annotation.
Immutable + Lombok + Jackson can be achieved in next way:
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.Value;
#Value
#NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
#AllArgsConstructor
public class LocationDto {
double longitude;
double latitude;
}
class ImmutableWithLombok {
public static void main(String[] args) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
String stringJsonRepresentation = objectMapper.writeValueAsString(new LocationDto(22.11, 33.33));
System.out.println(stringJsonRepresentation);
LocationDto locationDto = objectMapper.readValue(stringJsonRepresentation, LocationDto.class);
System.out.println(locationDto);
}
}
I tried several of the above and they were all temperamental.
What really worked for me is the the answer I found here.
on your project's root directory add a lombok.config file (if you haven't done already)
lombok.config
and inside paste this
lombok.anyConstructor.addConstructorProperties=true
Then you can define your pojos like the following:
#Data
#AllArgsConstructor
public class MyPojo {
#JsonProperty("Description")
private String description;
#JsonProperty("ErrorCode")
private String errorCode;
}
Here is an example by using
#Jacksonized annotation:
import lombok.Value;
import lombok.Builder;
import lombok.extern.jackson.Jacksonized;
#Jacksonized
#Builder
#Value
public class User {
String name;
String surname;
}
It does require you to use #Builder annotation.
I had exactly the same issue, "solved" it by adding the suppressConstructorProperties = true parameter (using your example):
#Value
#Wither
#AllArgsConstructor(suppressConstructorProperties = true)
public class TestFoo {
#JsonProperty("xoom")
private String x;
private int z;
}
Jackson apparently does not like the java.beans.ConstructorProperties annotation added to constructors. The suppressConstructorProperties = true parameter tells Lombok not to add it (it does by default).
It can be done simpler, without extra annotations and the problem can be with the inheritance, i.e. child classes should be deserializable as well. So, my example:
Requirements:
lombok.config inside the project root directory with body containing:
lombok.anyConstructor.addConstructorProperties=true
/** The parent class **/
#Value
#NonFinal
#SuperBuilder
#RequiredArgsConstructor
public class Animal {
String name;
}
/** The child class **/
#Value
#SuperBuilder
#RequiredArgsConstructor
public class Cat {
Long tailLength;
#ConstructorProperties({"tailLength", "name})
public Cat(Long tailLength, String name) {
super(name);
this.tailLength = tailLength;
}
}
It:
Allows building of objects including fields of the parent
Serializes/Deserializes with the default ObjectMapper and Jackson
Instances of the parent and children classes are immutable
My advice against other examples:
Try not to put custom annotations on particular classes, it makes it inhomogeneous. Any way, you will come to a generic solution one day.
Try not to put Jackson annotations on any fields on constructors, it creates coupling, when Jackson is capable to serialize/deserialize without any annotations.
Do not use #AllArgsConstructor for immutable entities. When your class has only final fields, conceptually right is #RequiredArgsConstructor, that's how you guarantee that class-clients always will rely only on a constructor with the immutable entity. Will #AllArgsConstructor it might lead to passing nulls.
I found two options to solve this problem if you want to use #Builder with Jackson.
Option 1
Add private default noArgs and allArgs constructors.
#Builder
#Getter
#Setter
#JsonIgnoreProperties(ignoreUnknown = true)
#AllArgsConstructor(access = AccessLevel.PRIVATE)
#NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Person {
#JsonProperty("user_name")
private String name;
}
Option 2
Thanks to this article.
Jackson expects the builder methods to start like .withProperty(...) but Lombok generates .property(...).
What you can do is to create the builder class yourself so that you can add Jackson annotations to it. Lombok will then re-use this class and add all the builder methods to it.
#JsonDeserialize(builder = MyDto.MyDtoBuilder.class)
#Builder
#Getter
public class MyDto {
#JsonProperty("user_id")
private String userId;
#JsonPOJOBuilder(withPrefix = "")
#JsonIgnoreProperties(ignoreUnknown = true)
public static class MyDtoBuilder {
}
}
You need to do some manual work
Still much better than writing the Builder yourself
Also note that additional properties like #JsonIgnorePropertie go on the builder
An additional drawback is that refactorings will not automatically rename the MyDtoBuilder. I hope in a future Lombok/Jackson version this issue is solved.
Update: I've found another solution (tested with lombok 1.18.20 and spring boot 2.4.5), added as Option 1.
#AllArgsConstructor(suppressConstructorProperties = true) is deprecated. Define lombok.anyConstructor.suppressConstructorProperties=true (https://projectlombok.org/features/configuration) and change POJO's lombok annotation from #Value to #Data + #NoArgsConstructor + #AllArgsConstructor works for me.
From Jan Rieke's Answer
Since lombok 1.18.4, you can configure what annotations are copied to
the constructor parameters. Insert this into your lombok.config:
lombok.copyableAnnotations += com.fasterxml.jackson.annotation.JsonProperty
Then just add #JsonProperty to your fields:
...
You'll need a #JsonProperty on every field even if the name matches, but that is a good practice to follow anyway. You can also set your fields to public final using this, which I prefer over getters.
#ToString
#EqualsAndHashCode
#Wither
#AllArgsConstructor(onConstructor=#__(#JsonCreator))
public class TestFoo {
#JsonProperty("xoom")
public final String x;
#JsonProperty("z")
public final int z;
}
It should also work with getters (+setters) though.
I've all my classes annotated like this:
#JsonAutoDetect(fieldVisibility = Visibility.ANY)
#JsonInclude(JsonInclude.Include.NON_DEFAULT)
#Data
#Accessors(fluent = true)
#NoArgsConstructor
#AllArgsConstructor
It worked with all Lombok and Jackson versions for, at least, a couple of years.
Example:
#JsonAutoDetect(fieldVisibility = Visibility.ANY)
#JsonInclude(JsonInclude.Include.NON_DEFAULT)
#Data
#Accessors(fluent = true)
#NoArgsConstructor
#AllArgsConstructor
public class Person {
String id;
String first;
String last;
}
And that's it.
Lombok and Jackson play together like a charm.
For me it worked when I have updated lombok version to:
'org.projectlombok:lombok:1.18.0'
I have managed to keep my classes immutable and also deserialize them by using this lombok annotation:
#NoArgsConstructor(force = true)
You can get Jackson to play with just about anything if you use its "mixin" pattern. Basically, it gives you a way to add Jackson annotations onto an existing class without actually modifying that class. I'm leaning towards recommending it here rather than a Lombok solution because this is solves a problem Jackson is having with a Jackson feature, so it's more likely to work long-term.
I would suggest you to use Gson as it does not give you all this hassle.
I added this in my spring boot app
spring.mvc.converters.preferred-json-mapper=gson
along with the dependency in maven and I solved all the problems. I didn't need to modify my lombok annotated pojos
#JsonInclude(JsonInclude.Include.NON_NULL)
#Data
public class Person {
String id;
String first;
String last;
}
Additional to the Data Class, it should be correct configured the ObjectMapper.
In this case, it is working ok with a ParameterNamesModule configuration, and setting visibility of Fields and Creator Methods
om.registerModule(new ParameterNamesModule());
om.setVisibility(FIELD, JsonAutoDetect.Visibility.ANY);
om.setVisibility(CREATOR, JsonAutoDetect.Visibility.ANY);
Then it should work as expected.
I was having issues with getting Lombok to not add the ConstructorProperies annotation so went the other way and disabled Jackson from looking at that annotation.
The culprit is JacksonAnnotationIntrospector.findCreatorAnnotation. Notice:
if (_cfgConstructorPropertiesImpliesCreator
&& config.isEnabled(MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES)
Also notice JacksonAnnotationIntrospector.setConstructorPropertiesImpliesCreator:
public JacksonAnnotationIntrospector setConstructorPropertiesImpliesCreator(boolean b)
{
_cfgConstructorPropertiesImpliesCreator = b;
return this;
}
So two options, either set the MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES to false or create a JacksonAnnotationIntrospector set setConstructorPropertiesImpliesCreator to false and set this AnnotationIntrospector into the ObjectMapper via ObjectMapper.setAnnotationIntrospector.
Notice a couple things, I am using Jackson 2.8.10 and in that version MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES does not exist. I am not sure in which version of Jackson it was added. So if it is not there, use the JacksonAnnotationIntrospector.setConstructorPropertiesImpliesCreator mechanism.
You need to have this module as well.
https://github.com/FasterXML/jackson-modules-java8
then turn on -parameters flag for your compiler.
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
I struggled with this for a moment as well. But looking through the documentation here
I can see that the onConstructor annotation parameter is considered experimental and is not supported well on my IDE (STS 4). According to the Jackson documentation, private members are not (de)serialized by default. There are quick ways to resolve this.
Add JsonAutoDetect annotation and set it appropriately to detect protected/private members. This is convenient for DTOs
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public class SomeClass
Add a factory function with #JsonCreator annotation, this works best if you need some object validation or additional transforms.
public class SomeClass {
// some code here
#JsonCreator
public static SomeClass factory(/* params here dressing them in #JsonProperty annotations*/) {
return new SomeClass();
}
}
Of course you could just manually add the constructor in yourself also as well.
Options which worked for me
This worked for me just by adding #AllArgsConstructor in my bean.
Add mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true); object mapper instance.
None of the above answers worked for me, but this below did.
What happens is that Jackson does not support the fluent getters, BUT you can tell it to use reflection to read the fields.
Try this:
#Value
#Accessors(chain = true, fluent = true)
#Builder(builderClassName = "Builder")
public static class TestFoo {
// ...
}
var foo = ...
var writer = new ObjectMapper()
.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
.writer();
writer.writeValueAsString(foo);
I had a different issue and it was with the boolean primitive types.
private boolean isAggregate;
It was throwing the following error as a result
Exception: Unrecognized field "isAggregate" (class
Lambok converts isAggregate to isAggregate() as a getter making the property internally to lombok as aggregate instead isAggregate. The Jackson library doesn't like it and it needs isAggregate property instead.
I updated the primitive boolean to Wrapper Boolean to work around this issue. There are other options for you if you are dealing with boolean types, see the reference below.
Sol:
private Boolean isAggregate;
ref: https://www.baeldung.com/lombok-getter-boolean

Categories