In Spring boot application with Lombok, I have pojo class AccountDTO
#Data
#Builder
#Accessors(fluent = true)
public class AccountDTO implements Serializable {
private String identification;
}
My project compiles fine. However, it throws an exception in its execution
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No
serializer found for class AccountDTO and no properties discovered to create
BeanSerializer
if I removed the annotation #Accessors(fluent = true), then it will work fine without any problems.
How can i make Lombok #Accessors(fluent = true) and Jackson work together ?
I stumbled on this question recently while encountering the same problem and the solution for me was to add and explicit #JsonProperty annotation on the getter generated by Lombok using:
#Getter(onMethod = #__(#JsonProperty)).
In your case, the class would look like this:
#Data
#Builder
#Accessors(fluent = true)
#Getter(onMethod = #__(#JsonProperty))
public class AccountDTO implements Serializable {
private String identification;
}
Related
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder(toBuilder = true)
public class Example {
public ExampleTwo exampleTwo;
#lombok.Data
#AllArgsConstructor
#NoArgsConstructor
#Builder(toBuilder = true)
public static class ExampleTwo {
private SomeData someData;
private AnotherField anotherField;
}
}
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder(toBuilder = true)
public class SomeData {
private String specialId;
private String firstName;
private String lastName;
}
So I retrieved an Example instance and made a deep copy to duplicate it. But I want to be able to set one of the fields of the copied object which is specialId from the SomeData nested class. My current working implementation is this:
SomeData someData = example.getExampleTwo().getSomeData().toBuilder().specialID("SPE_1").build();
Example.ExampleTwo exampleTwo = example.getExampleTwo().toBuilder().someData(someData).build();
Example duplicateExample = example.toBuilder().exampleTwo(exampleTwo).build();
Do you have thoughts on ways to make this cleaner without having to go through these additional steps? Would prefer it to be easier to read. I'm avoiding Serializable implementation and declaring it as a Cloneable interface since I've read to avoid those. Last resort would be to use a library.
#With might be helpful:
Example example = Example.builder().build();
Example.ExampleTwo exampleTwo = example.getExampleTwo();
SomeData someData = exampleTwo.getSomeData();
return example.withExampleTwo(
exampleTwo.withSomeData(
someData.withSpecialId("SPE_1")
)
);
I have two classes OAuth2Token and CachedOAuth2Token that extends a class called AbstractOAuth2Token.
AbstractOAuth2Token.java
#SuperBuilder
#Jacksonized
#JsonSubTypes({
#JsonSubTypes.Type(value = OAuth2Token.class),
})
#Getter
#Setter
#ToString
public abstract class AbstractOAuth2Token {
#JsonProperty("access_token")
private String accessToken;
#JsonProperty("token_type")
private String tokenType;
}
OAuth2Token.java
#Getter
#Setter
#SuperBuilder
#ToString(callSuper = true)
#JsonTypeName("OAuth2Token")
#Jacksonized
public class OAuth2Token extends AbstractOAuth2Token {
#JsonProperty("expires_in")
private int expiresIn;
}
CachedOAuth2Token.java
#Getter
#Setter
#SuperBuilder
#ToString(callSuper = true)
public class CachedOAuth2Token extends AbstractOAuth2Token {
private LocalDateTime expirationDate;
}
Unfortunately my Maven project doesn't build because AbstractOAuth2Token.java: Builders on abstract classes cannot be #Jacksonized (the builder would never be used).
Even if the code works as expected if the AbstractOAuth2Token isn't abstract, then I'm able to create an instance of it using the builder which indeed isn't what I want. Its constructor is protected, so no problem there.
The idea is that I want AbstractOAuth2Token to be abstract without losing any fuctionality in the children. I'm a fan of Lombok, so I want to be able to use the autogenerated builders but together with Jackson.
It's a Wildfly 11 project with Lombok 1.18.16
How can I solve this issue?
Don't add #Jacksonized to your abstract base class. Non-#Jacksonized #SuperBuilders are compatible with #Jacksonized #SuperBuilders. As Jackson will never use AbstractOAuth2Token's builder directly, there is no need to configure it for Jackson explicitly.
I am having a StackOverflowError Error when I am trying to map data from neo4j into Spring data. The project is using Lombok.
I am able to get Project with only one Use, That worked fine. But for a Project with two or more use, The error occur.
Can anyone help? Thanks.
Error:
Resolved exception caused by Handler execution: org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.StackOverflowError
Project.java
#NodeEntity
#Data
//#JsonIgnoreProperties({"project","use"}) //also tried but same issue
public class Project {
#Id
private String id;
#Nullable
private String name;
#JsonIgnoreProperties({"project","use"})
#Relationship(type = "USED_FOR")
List<ProjectUseRelation> usedFor = new ArrayList<>();
}
Use.java
#NodeEntity
#Data
//#JsonIgnoreProperties({"project","use"}) //also tried but same issue
public class Use {
#Id
private String id;
#Nullable
private String name;
#JsonIgnoreProperties({"project","use"})
#Relationship(type = "USED_FOR", direction = Relationship.INCOMING)
List<ProjectUseRelation> usedByProjects = new ArrayList<>();
}
ProjectUseRelation.java
#RelationshipEntity(type = "USED_FOR")
#Data
public class ProjectUseRelation {
#Id
private Long id;
#Nullable
private String name;
#StartNode
#JsonIgnoreProperties({"usedFor"})
private Project project;
#EndNode
#JsonIgnoreProperties({"usedByProjects"})
private Use use;
}
Service.java
#Service
#AllArgsConstructor
public class Service{
private final Repository repository;
#Transactional(readOnly = true)
public Map<String, Object> graph(String id){
//system correctly reached here
Collection<Project> result = repository.graph(id);
//system can't reach here
return result;
}
}
Repository.java
public interface Repository extends Neo4jRepository<Project,String>{
#Query("MATCH map = (p:Project)-[]-(u:Use) WHERE p.id = {id} RETURN map")
Collection<Project> graph(#Param("id") String id);
}
This now can return me single/multiple on-to-one relationships, but not single one-to-many relationship.
If you need to serialize your data to JSON, and your entities have circular dependencies (e.g., entity X has a field that references Y, and Y has a field that references X), then you must use the annotations #JsonIgnoreProperties or #JsonIgnore, where appropriate, to avoid StackOverflowErrors during serialization.
Refer to the documentation for more details.
[UPDATE]
Your Project entity ignores the ProjectUseRelation.project field (during serialization) but does not ignore ProjectUseRelation.use.
And the Use entity ignores ProjectUseRelation.use but does not ignore ProjectUseRelation.project.
Therefore, a circular dependency still exists:
Project.userFor
-> ProjectUseRelation.use
-> Use.usedByProjects
-> ProjectUseRelation.project
-> Project
So, I figured out that the issue is with Lombok #Data annotation. If I comment out #Data annotation and write my own getters and setters, there will not be a Stack Overflow Error.
#BasePathAwareController
public class MetricController {
#Autowired
private MetricRepository metricRepository;
#Transactional
#RequestMapping(method = RequestMethod.GET, value = "/metrics/in/{id}")
public #ResponseBody
MetricDTO getMetric(#PathVariable Long id) {
return MetricDTO.fromEntity(metricRepository.getOne(id));
}
}
#Entity
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Table(
uniqueConstraints = #UniqueConstraint(columnNames = {"metricType", "instanceType"}, name = "customUniqueId")
)
public class Metric implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true)
private String name;
private SourceType sourceType;
private String metricTypeField;
private String metricType;
private String instanceType;
private String instanceTypeField;
#ElementCollection
private List<String> metricIdFields;
#ElementCollection
private List<String> valueFields;
#ElementCollection
private Map<String, String> virtualFieldValueEx;
}
#Setter
#Getter
#NoArgsConstructor
#AllArgsConstructor
public class MetricDTO {
private SourceType sourceType;
private String metricTypeField;
private String metricType;
private String instanceType;
private String instanceTypeField;
private List<String> metricIdFields;
private List<String> valueFields;
private Map<String, String> virtualFieldValueEx;
public static MetricDTO fromEntity(Metric metric) {
return new MetricDTO(
metric.getSourceType(),
metric.getMetricTypeField(),
metric.getMetricType(),
metric.getInstanceType(),
metric.getInstanceTypeField(),
metric.getMetricIdFields(),
metric.getValueFields(),
metric.getVirtualFieldValueEx()
);
}
}
Since #RepositoryRestController in Spring Data Rest is not compatible with Swagger, I changed it to #BasePathAwareController.
So, the problem is that the controller is not working properly.
The error history is as follows.
Could not write JSON: failed to lazily initialize a collection of role: com.jmsight.management.entity.Metric.metricIdFields, could not initialize proxy - no Session; nested exception is com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: com.jmsight.management.entity.Metric.metricIdFields, could not initialize proxy - no Session (through reference chain: com.jmsight.management.dto.MetricDTO["metricIdFields"])
Using #RepositoryRestController works normally.
What is the problem? Is it resolvable?
If you check the source of #BasePathAwareController then you can see that it's not annotated with #Controller.
So if a class is annotated only with #BasePathAwareController then it won't be picked neither by the default SpringMvc neither RepositoryRestMvc.
The former picks classes annotated with #Controller or #RequestMapping, the latter picks only classes annotated with #RepositoryRestController.
So once again, just to make it clear: #BasePathAwareController is NOT an 'extension' of #Controller, it's just a additional 'sign' annotation.
You can use #Controller together with #BasePathAwareController instead of #RequestMapping too.
I believe it's a misleading naming, or simply a bug in the implementation.
One more thing.
If you switch the from #RepositoryRestController to #Controller/#RequestMapping, then your controller will be handled a completely different way.
It could look that it works the same way, but it's called by a completely different handlerMapping: which uses different converters, argumentResolvers, even a different objectMapper.
There could be unpleasant surprises if you need to implement more complicated handler-methods in your controller-class.
I solved it.
To share, #BasePathAwareController should be written in class as #RequestMapping.
I don't know why. If you know reason teach me please.
#BasePathAwareController
#RequestMapping(value = "your url value")
public class MetricController {}
Using Jackson Filters for the first time I was very surprised to see it not working at all. Here's the test code that I just wrote:
#Test
public void testJsonSerialization() throws JsonProcessingException {
Principal princi = Principal.builder().fullName("John Smith").role(Role.USER).build().setAccountType(CHILD)
.setClassAttended(Classes.C10);
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.filterOutAllExcept("fullName");
FilterProvider provider = new SimpleFilterProvider().addFilter("bareBonesFilter", filter);
JsonFilter jFilter = Principal.class.getAnnotation(JsonFilter.class);
assertNotNull(jFilter); // passes
assertEquals("bareBonesFilter", jFilter.value()); // passes
provider.findPropertyFilter("bareBonesFilter", princi); // doesn't throw exception, therefore a filter was found
ObjectWriter writer = (new ObjectMapper()).setSerializationInclusion(Include.NON_ABSENT)
.enable(SerializationFeature.INDENT_OUTPUT).writer(provider);
String json = writer.writeValueAsString(princi);
assertFalse(json.contains("accountType")); //this fails
}
Using Jackson 2.7.1.
What am I missing? Do I need to add some special configuration somewhere to enable use of filters?
Update 1
I tried the above code with 2.7.9 and 2.9.4 as suggested by #StaxMan, and neither one worked.
Then instead of using the Principal class (which has grown large & complex), I tried the above code with a simpler SgEmailInfo class (which is just a data holder) and the filtering worked as expected!
#Data
#Accessors(chain = true)
#JsonFilter("bareBonesFilter")
public class SgEmailInfo {
private long created;
private String email;
private String reason;
private String status;
}
#Data and #Accessors are Lombok annotations for auto-generating some code. The Principal class also uses these annotations but also other Lombok annotations:
#Entity //Objectify annotation
#Cache(expirationSeconds = 600) //Objectify annotation
#Data
#EqualsAndHashCode(of = "id", callSuper = false) // Lombok
#ToString(callSuper = true, doNotUseGetters = true, of = { "id", "firstName", "middleName",
"lastName", "profileName", "roles" }) // Lombok
#Accessors(chain = true)
#JsonFilter("bareBonesFilter")
public class Principal extends AccountEntity<Principal, EmailPrincipal>
implements Serializable, ProfileShort, ProfileMedium
I am also using #JsonIgnore annotation on many of the properties and getters of Principal.
There's also the #Builder annotation on a constructor of Principal:
#Builder
private Principal(String fullName, #Singular List<Role> roles)
Could any of these annotations or code generated by Lombok be interfering with the filtering?
Code seems legit to me. After trying out with a more recent version (to ensure it wasn't a bug that has since been fixed), could you file a bug against jackson-databind? It is possible I am missing some problem with above code, but it is nothing superbly obvious, so there's a chance it could be a bug.