Getting around Json jackson and lombok constructor requirements - java

Using json to save and load data requires a constructor for json to load the object, and I'm having trouble getting lombok annotations to work with this. What should I do?
This is what my class looked like before and after attempting to use an annotation to construct my item:
#Data
public class Item { //before
private int id;
private int amount;
public Item(#JsonProperty("id") int id, #JsonProperty("amount") int amount) {
this.id = id;
this.amount = amount;
}
}
#Data
#AllArgsConstructor
#NoArgsConstructor //I don't want this here as it could cause complications in other places. But json requires I have this...
public class Item { //after
private int id;
private int amount;
}
I don't want to use the NoArgsConstructor annotation by lombok as I don't want a no args constructor for this class. I realise that I could do this:
private Item() {
}
But was hoping there is a better way...

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:
#Data
#AllArgsConstructor
public class Item {
#JsonProperty("id")
private int id;
#JsonProperty("amount")
private int amount;
}
Although the annotation parameters may seem unnecessary, they are in fact required, because at runtime the names of the constructor parameters are not available.

try adding this to your lombok config file:
lombok.anyConstructor.addConstructorProperties=true
config.stopBubbling = true

So what you're saying is that Jackson requires no-args constructor for deserialization, and you don't want to add no-args constructors to your classes because that doesn't play well with your model.
Lombok is completely irrelevant here - it makes zero difference whether no-args constructor would be written manually or generated by Lombok, it'll still be just a no-args constructor.
Your real question is - can I make Jackson work without no-argument constructors on target classes. There are multiple answers to that already, you have almost done it. Here's what has to be done:
Add #JsonCreator annotation to your constructor
Add #JsonProperty("propName") to constructor parameters
You did the #2 but not #1. Add that and this should fix your problem.

Related

JsonProperty and lombok on Intellij

I'm running into an issue with my pojo created using lombok with jsonproperty annotation. It doesn't respect the json annotation. And, when i create an object using the lombok builder it uses the field names on the object instead of json property.
Could someone help see what am I missing here. I just started using lombok so im hoping something straightforward. I'm running the code on Intellij
#Data
#Builder
public class pojo {
#JsonProperty("grant_type")
private final String grantType = "xyz";
#JsonProperty("client_id")
private String clientId;
}
It's default behavior of #Builder.
If we want the builder with setClientId, We can add setterPrefix = "set" into #Builder.
#Data
#Builder(setterPrefix = "set")
public class pojo {
#JsonProperty("grant_type")
private final String grantType = "xyz";
#JsonProperty("client_id")
private String clientId;
}
#Data would generate a pair of setter/getter. But setter is a member method of pojo, not pojoBuilder's.
More details of Builder is here.

Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object

I am getting below error while using lombok and even it doesn't allow me to set id and version while creating student instance.
Multiple markers at this line
- overrides com.example.demo.IModel.canEqual
- Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is
intentional, add '#EqualsAndHashCode(callSuper=false)' to your type.
- overrides com.example.demo.IModel.hashCode
- overrides com.example.demo.IModel.toString
- overrides com.example.demo.IModel.equals
IModel
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class IModel {
private String id;
private String version;
}
Student
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class Student extends IModel{
private String firstName;
private String lastName;
}
In the main method, it doesn't allow me to set the value of Id and version field
Student s = Student.builder().firstName("Adam").lastName("Kerr").build();
Edit-1
#sfiss - As suggested, now I changed like below, but now I am not able to set firstName and lastName, only cab set id and version
Student.java
#Data
#Builder(builderMethodName = "studentBuilder")
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode(callSuper = true)
public class Student extends IModel {
#NotEmpty(message = "{email.notempty}")
#Email
private String firstName;
private String lastName;
public Student(final String firstName, final String lastName, final String id, final String version) {
super(id, version);
this.firstName = firstName;
this.lastName = lastName;
}
}
IModel.java
#Builder
#Data
#NoArgsConstructor
#AllArgsConstructor
public class IModel {
private String id;
private String version;
}
There are multiple problems here, all of them relating to using lombok with inheritance:
Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is
intentional, add '#EqualsAndHashCode(callSuper=false)' to your type.
The warning is given by #Data because that usually generates equals/hashcode without the call to super. Fix it by adding #EqualsAndHashCode(callSuper = true).
The #Builder gives you a compile warning because it will generate two static methods with the same name in both the super- and the subclass. Fix it by defining #Builder(builderMethodName = "studentBuilder") on Student.
You won't be able to set superclass properties on you studentBuilder because your superclass and subclass have a default constructor. Fix it by creating a constructor and moving the #Builder annotation to it (i.e. annotate the constructor with #Builder, not the class):
Code:
#Builder(builderMethodName = "studentBuilder")
public Student(
final String firstName,
final String lastName,
final String id,
final String version) {
super(id, version);
this.firstName = firstName;
this.lastName = lastName;
}
Call your builder with the correct method (IModel.builder() vs Student.studentBuilder()):
Student.studentBuilder().firstName("Name").build();
I also want to add some improvements to the above solution. While I like lombok as a tool (I really don't need to read that much boilerplate), the first solution to preventing boilerplate is to think whether you need all those getters and setters and ask yourself these questions:
Do you want bags of data? It is fine for some use cases, in others you want objects more in the sense of OOP, i.e. don't expose your state but behavior.
Do you really need mutability? If not, prefer #Value.
Do you really need both constructor types (especially the no-args-constructor)? They are part of the problem here. Sometimes you need them for frameworks (proxies, reflection, ...) to work properly.
More specific to your code: You prefixed the superclass with "I" but it is not an interface. If it is meant as an abstract class, declare it abstract and don't give it a #Builder.
You can use #sfiss solution
or
You can use #Getter and #Setter annotations instead of #Data annotation.
The warning from building a Spring Boot project
When I built my Spring boot project, I got 20 warnings about a same thing. The warning shows: Generating equals/hashCode implementation but without a call to superclass
Description of the warning
This warning is from lombook, it happens when we inherit a child class from parent class by using #Data #ToString #EqualsAndHashCode, IDE will trigger the warning: Generating equals/hashCode implementation but without a call to superclass .
Solution
There are two solutions:
add annotation #EqualsAndHashCode(callSuper = true) on the class
create lombook config file in the project root path: src/main/java. Note: this solution requires the version of lombook > 1.14.
I recommend the solution 2, since you will not need to add the annotation to all the required classes.
To impletement the solution, you need to create lombok.config in the path of src/main/java. If you have more than one packages, you may need to create multiple config files.
The content of the config file includes:
config.stopBubbling=true
lombok.equalsAndHashCode.callSuper=call
When we rebuild our project, you will not get these warnings anymore.
Cheers!
I had the same problem and i resolved it in this way hope it helps you.
That help you also with abstract classes
Student.java
#Data
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode(callSuper = true)
public class Student extends IModel {
#NotEmpty(message = "{email.notempty}")
#Email
private String firstName;
private String lastName;
#Builder
public Student(final String firstName, final String lastName, final String id, final String version) {
super(id, version);
this.firstName = firstName;
this.lastName = lastName;
}
}
IModel.java
#Data
#NoArgsConstructor
#AllArgsConstructor
public class IModel {
private String id;
private String version;
}
The error says If this is
intentional, add '#EqualsAndHashCode(callSuper=false)' to your type.
So, add #EqualsAndHashCode(callSuper = false) to solve the problem.

Rest application using hateoas: Multiple markers at this line

I am trying to make a rest application using hateoas.
Here is my assembelr class:
public class PlantInventoryEntryAssembler extends ResourceAssemblerSupport<PlantInventoryEntry, PlantInventoryEntryDTO>
{
}
PlantInventoryEntryDTO class is:
#Data
public class PlantInventoryEntryDTO extends ResourceSupport{
Long id;
String name;
String description;
#Column(precision = 8, scale = 2)
BigDecimal price;
public Long idGetter (){
return id;
}
}
the problem is, in line #Data (i use lombok) i have faced with the following error:
Multiple markers at this line
- overrides org.springframework.hateoas.ResourceSupport.equals
- The return type is incompatible with ResourceSupport.getId()
- Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If
this is intentional, add '#EqualsAndHashCode(callSuper=false)' to your type.
- overrides org.springframework.hateoas.ResourceSupport.toString
- overrides org.springframework.hateoas.ResourceSupport.hashCode
- overrides org.springframework.hateoas.ResourceSupport.getId
How can i handle it?
Update:
PlantInventoryEntry class
#Entity
#Data
public class PlantInventoryEntry {
#EmbeddedId
PlantInventoryEntryID id;
String name;
String description;
#Column(precision = 8, scale = 2)
BigDecimal price;
}
Rename the field id in PlantInventoryEntryDTO (to, say, entryId).
The only error in those "multiple markers" is The return type is incompatible with ResourceSupport.getId(), the others are warnings.
ResourceSupport defines a getId() method with a return type of Link. Lombok tries to add another getId() method with the return type long for the id field in PlantInventoryEntryDTO. Since their return types are incompatible, the compiler won't override the original method.

Java Lombok: Omitting one field in #AllArgsConstructor?

If I specify #AllArgsConstructor using Lombok, it will generate a constructor for setting all the declared (not final, not static) fields.
Is it possible to omit some field and this leave generated constructor for all other fields?
No that is not possible. There is a feature request to create a #SomeArgsConstructor where you can specify a list of involved fields.
Full disclosure: I am one of the core Project Lombok developers.
Alternatively, you could use #RequiredArgsConstructor. This adds a constructor for all fields that are either #NonNull or final.
See the documentation
Just in case it helps, initialized final fields are excluded.
#AllArgsConstructor
class SomeClass {
final String s;
final int i;
final List<String> list = new ArrayList<>(); // excluded in constructor
}
var x = new SomeClass("hello", 1);
It makes sense especially for collections, or other mutable classes.
This solution can be used together with the other solution here, about using #RequiredArgsConstructor:
#RequiredArgsConstructor
class SomeClass2 {
final String s;
int i; // excluded because it's not final
final List<String> list = new ArrayList<>(); // excluded because it's initialized
}
var x = new SomeClass2("hello");
A good way to go around it in some cases would be to use the #Builder
This can be done using two annotations from lombok #RequiredArgsConstructor and #NonNull.
Please find the example as follows
package com.ss.model;
import lombok.*;
#Getter
#Setter
#RequiredArgsConstructor
#ToString
public class Employee {
private int id;
#NonNull
private String firstName;
#NonNull
private String lastName;
#NonNull
private int age;
#NonNull
private String address;
}
And then you can create an object as below
Employee employee = new Employee("FirstName", "LastName", 27, "Address");
Lombok is meant to take care of the boilerplate code for your POJOs. Customized constructors/setters/getters/toString/copy etc are not on the boilerplate side of code. For these cases, every Java IDE provide easy to use code generators to help you do things in no time.
In your case a
public MyClass(String firstName, String lastName) {....}
is much more readable and makes more sense than a hypothetic:
#AllArgsConstructor(exclude = "id", exclude = "phone")
Have fun!

Using embedded nested class to represent complicated state in JPA

I am trying to use an inner class as embeddable to represent some complicated properties of the outer class. When I store this, there is no information from the inner class in the database schema generated by eclipselink.
Does what I'm trying to do seem like a good idea? Why doesn't eclipselink seem to recognize them #Basic attribute on the getRate() in Attributes?
Some other info: Measure must be instantiated using a factory which is provided to the constructor of Person, so I don't even know how I'm going to be able to use this at all. It seems more and more likely that I'll have to make a separate class just to store the state of Person in simple terms (like doubles, not Measures) and use those to create the real Person-type objects, but that has very sad implications for the rest of my application.
#Entity
public static class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#Transient
public Measure<Double, CleanupRate> rate;
#Embedded
private Attributes attributes;
#Embeddable
public static class Attributes {
#Transient
private Person person;
public Attributes() {
}
public Attributes(Person person) {
this.person = person;
}
#Basic
public double getRate() {
return person.rate.getInternalValue();
}
public void setRate(double value) {
person.rate.setInternalValue(value);
}
}
public Person() {
rate = udm.getMeasureFactory().createMeasure(0.0, CleanupRate.class);
attributes = new Attributes(this);
}
public void setRate(double rate) {
this.rate.setValue(rate);
}
}
Edit:
In order to inject the measure dependency into my objects when they are retrieved from storage, I've added an interface which injects the dependency and used it in my DAO. Since the DAO can be injected, I can propagate the dependency down to the retrieved objects. I got the idea from a blog.
private <T extends UomInjectable> List<T> //
getListOfUomInjectableType(final Class<T> klass) {
List<T> result = getListOfType(klass);
for (UomInjectable injectable : result) {
injectable.injectUomFactory(udm);
}
return result;
}
It is using the access type from the Person class, which is set to field, and so not seeing the annotation at the property level.
You will need to change the access type using Access(PROPERTY) on the embeddable class, and should remove the #Transient annotation on the person attribute.
I think in general you're going to be in trouble having Entities (Embeddable or otherwise) that need constructors with arguments. I'm not sure how that might be related to your schema generation issue, but I think this will be a problem trying to persist/retrieve these objects.
As you hinted, JPA requires all entity types to have a no-argument constructor. While your Attributes class has one, it leaves the 'person' field as null which will fairly quickly result in NPE's. Same with the Person constructor (maybe you left out the one that passes in 'udm' from the sample code?).
The set the Person for the Attributes, just use property access in Person and set it in your setAttributes method.
See,
http://en.wikibooks.org/wiki/Java_Persistence/Embeddables#Relationships

Categories