JsonDeserialize set inherited properties also when objectmapper readvalue - java

JsonDeserialize not working when objectmapper readvalue for inherited properties.
Vehicle Class
#Getter
#Setter
#JsonDeserialize(builder = Vehicle.VehicleBuilder.class)
#Builder(builderClassName = "VehicleBuilder", toBuilder = true)
public class Vehicle{
private String name;
private String noOfTyres;
#JsonPOJOBuilder(withPrefix = "")
public static class VehicleBuilder{
}
}
Car class
#Getter
#Setter
#JsonDeserialize(builder = Car.CarBuilder.class)
#Builder(builderClassName = "CarBuilder", toBuilder = true)
public class Car extends Vehicle {
private String carType;
#JsonPOJOBuilder(withPrefix = "")
public static class CarBuilder extends VehicleBuilder {
}
}
I don't want to create #NoArgsConstructor ,#AllArgsConstructor in both classes.
My issue when Car car = om.readValue(jsonValue,Car.class);
When I parse Json to java object the parent class properties are not setting properly.
As of now I'm using #NoArgsConstructor ,#AllArgsConstructor for work around for the use case.
Is there any way to use it along with #JsonDeserialize and #JsonPOJOBuilder?

The problem with the code is that it assumes that builders in inherited classes will set the parent properties as well. Unfortunately, they don't do that out of the box. However, this is something that can be achieved with Lombok but requires some additional code, as described in this post.
A complete solution could look as follows.
Parent Class
#Getter
#Setter
#JsonDeserialize
#Builder(builderClassName = "VehicleBuilder", builderMethodName = "vehicleBuilder")
public class Vehicle {
private String name;
private String noOfTyres;
}
Child Class
#Getter
#Setter
#JsonDeserialize(builder = Car.CarBuilder.class)
public class Car extends Vehicle {
private String carType;
#Builder
public Car(String name, String noOfTyres, String carType) {
super(name, noOfTyres);
this.carType = carType;
}
#JsonPOJOBuilder(withPrefix = "")
public static class CarBuilder extends VehicleBuilder {
}
}
Notice that the builder on the extending class is achieved by supplying a constructor with the #Builder annotation. Also take notice that the extending class does not set annotation parameter toBuilder=true as that will require access to parent properties which are private. This can be achieved by setting parent class properties to protected.

Related

Deep copy with lombok toBuilder within a nested class - cleaner way?

#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")
)
);

Cannot map nested field with #Mapper

I'm trying to create a mapper using lombok #Mapper and I get the following error:
Property "delivery" has no write accessor in ClassA for target name "classBB_variable.delivery"
The code:
#Mapper
#Mapping(target = "classBB_variable.customerId", source = "classA_variable.customerId")
#Mapping(target = "classBB_variable.delivery", source = "classA_variable.classAA_variable.delivery")
ClassB toCreateRequest(ClassA classA_variable);
#Value
#Builder(toBuilder = true)
public class ClassA {
private UUID customerId;
private ClassAA classAA_variable;
}
#Data
#Builder
public class ClassAA {
private String delivery;
}
#Data
#Builder
public class ClassB {
private ClassBB classBB_variable;
}
#Data
#Builder
public class ClassBB {
private UUID customerId;
private String delivery;
}
As the map for customerId works fine, I'm assuming the issue is the additional level introduced with classA_variable.classAA_variable.delivery.
Has anyone already dealt with this situation? How can I solve it?

How to tell MapStruct "not" to use the Lombok Builder?

I have the following classes and mapper to map them.
How can I configure Mapstruct to "not" use the Lombok builder? (without removing the #Builder annotation)?
When using the latest version of Lombok and mapstruct, mapstruct will automatically use the Builder when the #Builder annotation is used. I can not find a way to disable that, as I need the Instance in an #AfterMapping method as the builder doesn't expose all required methods (#SuperBuilder is not allowed in this use case)
#Entity(name = "user_details")
#Data
#Builder
public class User extends AuditableEntityBase {
#Version
#NotNull
private Integer version;
#NotNull
private String name;
#NotNull
private String email;
#NotNull
private Address address; // Just another Class containing another class that is mapped as well.
}
#Value
#Builder
public class UserDto extends AuditableEntityBaseDto {
#NotNull
private Integer version;
#NotNull
private String name;
#NotNull
private String email;
#NotNull
private AddressDto address;
}
#Mapper(componentModel = "spring")
class UserRestMapper {
public abstract UserDto map(User obj);
}
#AfterMapping
public void decorate(User source, #MappingTarget AuditableEntityBase target) {
// Method is never called.
// Method is called in case the second argument is: "#MappingTarget UserDto.UserDtoBuilder target"
}
If you want to disable using builders you can do so by adding #Builder(disableBuilder = true) to your #Mapper.
e.g.
#Mapper(componentModel = "spring", builder = #Builder(disableBuilder = true))
class UserRestMapper {
public abstract UserDto map(User obj);
}
Note #Builder is from the org.mapstruct package
No idea about disabling it, but why not do it like this?
#Mapper(componentModel = "spring")
abstract class UserRestMapper {
public abstract UserDto map(User obj);
public UserDto decoratedMap(User obj) {
UserDto mapped = map(obj);
// your after mapping logic
return mapped;
}
}

Lombok #SuperBuilder doesn't take parameters

Assuming two simple classes:
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode
public class Party {
protected Long id;
protected String status;
}
#Data
#SuperBuilder
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode(callSuper = true)
public class Person extends Party {
private String name;
private Long sex;
}
The compilation fails on the following error. Upon reading Lombok | #SuperBuilder I have no idea what I could miss.
C:\Dev\companyproject\src\main\java\com\companyproject\entity\Person.java:12
java: type com.companyproject.entity.Party.PartyBuilder does not take parameters
The issue here is the incorrect #Builder annotation on the parent class. The documentation for #SuperBuilder mentions:
Most importantly, it requires that all superclasses also have the #SuperBuilder annotation.
So the correct parent class would be:
#Data
#SuperBuilder // <- annotation replaced here
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode
public class Party {
protected Long id;
protected String status;
}
Addendum:
A key difference between both annotations is that #SuperBuilder also creates a constructor of the class expecting a corresponding builder argument.
For Party it would look like:
protected Party(PartyBuilder<?, ?> b) {
this.id = b.id;
this.status = b.status;
}
and for Person:
protected Person(PersonBuilder<?, ?> b) {
super(b);
this.name = b.name;
this.sex = b.sex;
}
As you can see, the child class constructor wants to pass its own builder to the parent class constructor and this will only be possible if there is a matching constructor there, and #Builder wouldn't generate it.
Also PersonBuilder<> extends PartyBuilder<>, that is why calling super with the child type builder works fine here.

Jackson #JsonIgnoreProperties isn't working on referenced class property?

I am trying to make a model (Request) class that would ignore a nested class's property it references. And I was told that I cannot modify referenced model (User) in any way, so what I had tried was to ignore its property from the Request model.
#Data
#Builder
public class Request {
#JsonIgnoreProperties(value = {"id"})
User user;
}
// class I cannot modify
#Data
#Builder
public class User {
String id;
String name;
...
}
In the payload, I am still seeing id serialized & deserialized so the annotation clearly doesn't work. It seems to work fine if I place it at the class level of User but since I cannot modify User, I've also tried:
#JsonIgnoreProperties(value = {"user.id"})
public class Request {
User user;
None of above works. The doc is saying
Starting with 2.0, this annotation can be applied both to classes and to properties
I am using Jackson 2.10.2 in a Spring Boot project. What am I missing?
Create a class who extends User as bellow:
public class MyUser extends User {
#JsonIgnore
String id;
}
#Data
#Builder
public class Request {
MyUser user;
}
I'm running this test and works:
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class Request {
#JsonIgnoreProperties("id")
User user;
}
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class User {
String id;
String name;
}
public class TestClass {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper= new ObjectMapper();
final Request value = Request.builder()
.user(User.builder()
.id("qw423432")
.name("asdasdjsadjasdasd")
.build())
.build();
System.out.println(mapper.writeValueAsString(value));
System.out.println(mapper.readValue("{\"user\":{\"id\": \"a\", \"name\":\"b\"}}", Request.class));
}
}
Do you see any difference with your test?

Categories

Resources