How to deserialize Java Optional field with lombok builder? - java

I want to deserialize Java Optional field in lombok builder. Below is my code
#JsonDeserialize(builder = AvailabilityResponse.Builder.class)
#Getter
#ToString
#EqualsAndHashCode
#Builder(setterPrefix = "with", builderClassName = "Builder", toBuilder = true)
public class AvailabilityResponse {
private final List<Train> trainDetails;
private final String name;
private final Optional<String> detail;
public static class Builder {
#JacksonXmlProperty(localName = "TRAIN")
private List<Train> trainDetails;
#JacksonXmlProperty(localName = "NAME")
private String name;
#JacksonXmlProperty(localName = "DETAIL_TRAIN", isAttribute = true)
private String detail;
public AvailabilityResponse build() {
return new AvailabilityResponse(
trainDetails, // I have field validation here. if null or empty throwing Exception
name, // I have field validation here. if null or empty throwing Exception
Optional.ofNullable(detail)); // This is Optional field
}
}
}
If I override the builder method like below, able to deserialize
private Optional<String> name; // this is static builder class field
#JacksonXmlProperty(localName = "DETAIL_TRAIN", isAttribute = true)
public Builder detail(final String theDetail) {
this.detail = Optional.ofNullable(theDetail);
return this;
}
I have used setterPrefix ="with" in #Builder. But if I override the above method with "with" prefix, its not working.
Please someone help me to acheive this

Jackson supports Optional using its jackson-datatype-jdk8 module. You can simply add it to the <dependencies> section in your pom.xml:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
<version>2.10.3</version>
</dependency>
Then, initialize your mapper as follows:
ObjectMapper mapper = new XmlMapper().registerModule(new Jdk8Module());
That mapper will automatically detect Optionals: If the XML field has a value, it will be wrapped in an Optional; if the XML field is empty (i.e., <DETAIL_TRAIN/>), then the result will be an Optional.empty(). (In your case you have an attribute; those cannot be empty.)
Jackson maps fields or attributes that do not exist in the XML to null (or, more precisely, it simply does not call the setter, so the field value will still be the default). If you also want to have an empty Optional in that case, you need to set the field default to Optional.empty() and add a #Builder.Default to that field.
Finally, lombok can automatically copy your annotations from the fields to the generated builder. You need to tell lombok which annotations to copy using a lombok.config file in your project root containing:
lombok.copyableAnnotations += com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty
config.stopBubbling = true
This means you do not need to customize your builder class at all:
#JsonDeserialize(builder = AvailabilityResponse.Builder.class)
#Getter
#ToString
#EqualsAndHashCode
#Builder(setterPrefix = "with", builderClassName = "Builder", toBuilder = true)
public class AvailabilityResponse {
#JacksonXmlProperty(localName = "TRAIN")
#JsonProperty("TRAIN")
private final List<Train> trainDetails;
#JacksonXmlProperty(localName = "NAME")
#JsonProperty("NAME")
private final String name;
#JacksonXmlProperty(localName = "DETAIL_TRAIN", isAttribute = true)
#JsonProperty("DETAIL_TRAIN")
#Builder.Default
private final Optional<String> detail = Optional.empty();
}
Note that you also need #JsonProperty annotations on your fields due to a Jackson bug.
If you want to validate field values, you should do that in the constructor, not in the builder. In this way you'll also catch those instantiations where the builder is not used. To do so, simply implement the all-args constructor yourself; the build() method will use it automatically:
private AvailabilityResponse(List<Train> trainDetails, String name, Optional<String> detail) {
this.trainDetails = Objects.requireNonNull(trainDetails);
this.name = Objects.requireNonNull(name);
this.detail = Objects.requireNonNull(detail);
}
I suggest making this constructor private, because
it enforces users of your class to use the builder, and
it is bad style to have Optionals as parameters in your public API.

Related

Different #JsonProperty config for READ and WRITE on same field?

I have a class (which cannot be modified) like
public class Standing {
private Integer positionNumber;
private String positionText;
private BigDecimal points;
..
}
When deserializing I get data like:
{
"position": "1",
"points": 10
}
As I cannot modify the Standing class I have a mix-in like:
#JsonDeserialize(converter = StandingSanitizer.class)
public abstract class StandingMixIn {
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
Integer positionNumber;
#JsonProperty(value = "position", access = JsonProperty.Access.WRITE_ONLY)
String positionText;
}
As the received json does not have positionNumber and positionText fields I use the #JsonPropery annotations.
With Access.READ_ONLY I simply ignore the positionNumber field.
And with #JsonProperty(value = "position", access = JsonProperty.Access.WRITE_ONLY) on the positionText field I make sure it's populated with the position field from the json during deserialization.
This works well during deserialization.
Note the StandingSanitizer sets the positionNumber. This as the received position value can be non-number values like DSQ in which case the positionNumber field will be null.
But when serializing I want to output all 3 fields from the Standing class like:
{
"positionText": "1",
"positionNumber": 1,
"points": 10
}
But because of #JsonProperty(value = "position", access = JsonProperty.Access.WRITE_ONLY) on the positionText field it is not serialized unfortunately.
In theory I would like to do have something like:
#JsonDeserialize(converter = StandingSanitizer.class)
public abstract class StandingMixIn {
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
Integer positionNumber;
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
#JsonProperty(value = "position", access = JsonProperty.Access.WRITE_ONLY)
String positionText;
}
where I could use different #JsonProperty annotation for both READ and WRITE.
But this is not possible as duplicate #JsonProperty annotations on a field are not allowed; and as far as I could see there is no support for repeatable annotations.
Is there any other solution to solve this?
One thing I can think of is to have 2 ObjectMapper instances, with 2 different StandingMixIns; 1 for deserializing and 1 for serializing. But I would prefer to keep having 1 ObjectMapper instance, so using 2 would be a last resort.
Thanks #Franjavi, you are indeed right I should use annotations on the getters/setters and not on the field only. I was to focussed on only using the fields as my mix-in classes are written in Groovy with implicit getters/setters.
I slimmed down the class a bit more to just:
public abstract class StandingMixIn {
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
Integer positionNumber;
#JsonProperty(value = "positionText")
public abstract String getPositionText();
#JsonProperty(value = "position")
public abstract void setPositionText(String positionText);
}
(removing the positionText completely and using abstract methods)
You could use the getters and setters to get that extra customization. The get will act as READ and the set as WRITE. Note that you don't need the access properties or the field level annotation:
public abstract class StandingMixIn {
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
Integer positionNumber;
// No annotation on the field
String positionText;
#JsonProperty(value = "positionText")
public String getPositionText() {
return positionText;
}
#JsonProperty(value = "position")
public void setPositionText(String positionText) {
this.positionText = positionText;
}
}

How do I use lombok #Builder to store different values?

I have the following JPA entity
#Data
#Builder
public class Post {
#Id
#GeneratedValue
UUID id;
#OneToMany
Set<PostTags> tags;
String content;
}
#Data
public class PostTag {
#Id
#GeneratedValue
UUID id;
#OneToOne
Post post;
String tag;
}
Using lombok #Builder I want to be able to do the following
Post post = Post.builder()
.tags("hello", "world")
.content("Hello world")
.build();
I am presuming I need a custom builder along the lines of
public static class PostBuilder {
private Set<String> myTags = new HashSet<>();
public PostBuilder tags(String... tags) {
myTags.addAll(Arrays.asList(tags));
return this;
}
}
From the documentation it appears there ObtainVia annotation that I can use, but I am not sure how to get around it (no example on the doc) and especially since I only want myTags to be a builder specific thing, and not be exposed on the main class itself.
ObtainVia only works for toBuilder, so that won't help much in this case.
I suggest the following approach.
First, add a factory method in PostTag, e.g. createTag(String). This method only sets tag in the instance it creates and leaves everything else null. Statically import it into the class where you want to use PostBuilder.
Next, use #Singular on tags. Then you can write:
Post post = Post.builder()
.tag(createTag("hello"))
.tag(createTag("world"))
.content("Hello world")
.build();
Finally, customize the build() method so that it first creates the Post instance (like an uncustomized build() method would) and then sets this newly created Post instance as post in all PostTag instances.
Have a look at the delomboked code to make sure you use the right builder class and method headers when customizing the builder.
You can use #Accessors for what you're asking:
Post
#Data
#Accessors(chain = true)
public class Post {
#Id
#GeneratedValue
private UUID id;
#OneToMany
private Set<PostTags> tags;
private String content;
public Post tags(String... tags) {
Arrays.stream(tags)
.map(tag -> PostTags.builder().tag(tag).build())
.forEach(this.tags::add);
return this;
}
}
PostTags
#Data
#Builder
public class PostTags {
#Id
#GeneratedValue
private UUID id;
#OneToOne
private Post post;
private String tag;
}
When you using #Accessors(chain = true), The setters will return this reference instead of void, and then your code will act this way:
Post post = new Post().setId(id).tags("aaa", "bbb");
If you want your code to be more similar to builder then add fluent value to the annotation: #Accessors(chain = true, fluent = true)
It will remove the set<Something> from the setters and just use the name of the fields, and then your code will look like this:
Post post = new Post().id(id).content("hello").tags("aaa", "bbb");

how to convert from String to enum during BeanUtils.copyProperties in Spring Boot Rest API

I have to copy the properties from dto to entity class.
I am using BeanUtils.copyProperties().
In request body I am sending like below:
{
"userName":"test",
"userStatus": "I",
}
DTO class:
public class UserDto {
private String userName;
private String userStatus;
public User buildUser() {
User user = new User();
BeanUtils.copyProperties(this, user);
return user;
}
}
Entity class:
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id")
private Long userId;
#Column(name = "user_name")
private String userName;
#Enumerated(EnumType.STRING)
#Column(name = "user_status")
private UserStatus userStatus;
}
note: userStatus can be nullable field in table.
Service code:
User user = userDto.buildUser();
I am getting userStatus value as null in User entity class.
When I changed UserDto.userStatus to enum type, then request body is not accepting empty value.
How do I convert from String to enum during BeanUtils.copyProperties() ?
Spring BeanUtils is not designed for such customizations.
You should set the field manually with.
While MapStruct or Dozen are.
As alternative to keep BeanUtils and no explicit setter invocation you have :
defining a factory method for the enum Jackson processing (a static method annotated #JsonCreator in the enum class such as :
#JsonCreator public static UserStatus getValue(String name) {
return
Stream.of(UserStatus.values())
.findAny(s -> s.name().equals(name))
.orElse(null);
}
In most of cases, this is the best solution as it handles the issue at the root.
setting the flag to ignore unknown value for any field of the class:
public class UserDto {
#JsonIgnoreProperties(ignoreUnknown = true)
//...
}
Fastest solution but I don't like a lot as it may hide some other serialization/deserialization issues.
adding an enum value representing the emptiness. You could so define the enum in the DTO.
In order to not store it in the database, the mapping of this enum value to null should be done in the entity itself.
For example :
public void setUserStatus(UserStatus userStatus){
if (userStatus != UserStatus.EMPTY){
this.userStatus = userStatus;
}
}
It should work but I am not a big fan either...
Enums cannot be null because their underlining values are int but you can set the FIRST value in the enum as a default value. tyou can also define your field in DTO as an Enum type instead of String.
UserStatus
public enum UserStatus {
NULL,
ACTIVE,
INACTIVE;
}
Service code:
userDto.setUserStatus(UserStatus.NULL);
userDto.buildUser();
OR If you want to set this override of copyProperties method to ignore userStatus field while converting:
public static void copyProperties(Object source, Object target,
#Nullable Class<?> editable,
#Nullable String... ignoreProperties);

Direct self-reference leading to cycle Superclass issue JSON

I have tried several things I found while searching but nothing helped or I did not implement it correctly.
Error I'm getting
Direct self-reference leading to cycle (through reference chain: io.test.entity.bone.Special["appInstance"]->io.test.entity.platform.ApplicationInstance["appInstance"])
Both these extend the base entity and in the base (super class) it has an appInstance as well.
Base entity looks similar to this
#MappedSuperclass
public abstract class BaseEntity implements Comparable, Serializable {
#ManyToOne
protected ApplicationInstance appInstance;
//getter & setter
}
Application entity looks like this
public class ApplicationInstance extends BaseEntity implements Serializable {
private List<User> users;
// some other properties (would all have the same base and application instance . User entity will look similar to the Special.)
}
Special entity
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "objectType")
#JsonIgnoreProperties({"createdBy", "appInstance", "lastUpdatedBy"})
public class Special extends BaseEntity implements Serializable {
#NotNull
#Column(nullable = false)
private String name;
#Column(length = Short.MAX_VALUE)
private String description;
#NotNull
#Column(nullable = false)
private Double price;
#OneToOne
private Attachment image;
#Enumerated(EnumType.STRING)
#ElementCollection(targetClass = SpecialTag.class)
#CollectionTable(name = "special_tags")
#Column(name = "specialtag")
private List<SpecialTag> specialTags;
#Temporal(TemporalType.TIME)
private Date specialStartTime;
#Temporal(TemporalType.TIME)
private Date specialEndTime;
#Enumerated(EnumType.STRING)
#ElementCollection(targetClass = WeekDay.class)
#CollectionTable(name = "available_week_days")
#Column(name = "weekday")
private List<WeekDay> availableWeekDays;
#OneToMany(mappedBy = "special", cascade = CascadeType.REFRESH)
private List<SpecialStatus> statuses;
#OneToMany(mappedBy = "special", cascade = CascadeType.REFRESH)
private List<SpecialReview> specialReviews;
#Transient
private Integer viewed;
private Boolean launched;
#OneToMany(mappedBy = "special")
private List<CampaignSpecial> specialCampaigns;
#Override
#JsonIgnore
public ApplicationInstance getAppInstance() {
return super.getAppInstance();
}
}
All entities in Special inherits from BaseEntity which contains AppInstance
then i have a method to get the special
#GET
#Path("{ref}")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(value = MediaType.TEXT_PLAIN)
public Special findByGuestRef(#PathParam("ref") String pRefeference) {
// find the special and return it
return special;
}
On the special entity I tried the following
Added jsonIgnoreProperties
Added an override for appInstance to annotate with #JsonIgnore
#JsonIdentityInfo
links for the above
https://stackoverflow.com/a/29632358/4712391
Jackson serialization: how to ignore superclass properties
jackson self reference leading to cycle
none of those solutions works. Am I doing something wrong?
Note: Would it also just be possible to edit special, since the other entities are in a different package and would not like to edit them.
Usually excluding attributes in a response is as easy as adding a #JsonIgnore annotation to their getters, but if you don't want to add this annotation to a parent class, you could override the getter and then add the annotation on it:
public class Special extends BaseEntity implements Serializable {
...
#JsonIgnore
public ApplicationInstance getAppInstance() {
return this.appInstance;
}
...
}
NOTE: As there are several frameworks, make sure that you are using the correct #JsonIgnore annotation or it will be ignored, see this answer for instance.
Another option, more "manual", is just creating a bean for the response which would be a subset of the Special instance:
#GET
#Path("{ref}")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(value = MediaType.TEXT_PLAIN)
public SpecialDTO findByGuestRef(#PathParam("ref") String pRefeference) {
// find the special and return it
return new SpecialDTO(special);
}
public class SpecialDTO {
//declare here only the attributes that you want in your response
public SpecialDTO(Special sp) {
this.attr=sp.attr; // populate the needed attributes
}
}
To me, problem seems to be in the Special object and the fields being initialized in it.
I guess that there is a circular reference detected when serialisation happens.
Something similar to:
class A {
public A child;
public A parent;
}
A object = new A();
A root = new A();
root.child = object;
object.parent = root;
In the above code, whenever you will try to seralize either of these objects, you will face the same problem.
Note that public fields are not recommended.
I'll suggest to peek into your Special object and the references set in it.

Explicit constructor using Lombok?

I'm rewriting some messy code that manages a database, and saw that the original programmer created a class mapped to the database like so:
(I've removed unnecessary code that has no purpose in this question)
#Entity
#Data
#EqualsAndHashCode(callSuper = false, of = { "accessionCode", "header", "date" })
#SuppressWarnings("PMD.UnusedPrivateField")
public class PDBEntry implements Serializable {
#Id
#NaturalId
#NotEmpty
#Length(max = 4)
private String accessionCode;
#NaturalId
#NotEmpty
private Date date;
#NaturalId
// We allow for the header to be 'null'
private String header;
private Boolean isValidDssp;
#Temporal(TemporalType.TIMESTAMP)
private Date lastUpdated = new Date(System.currentTimeMillis());
protected PDBEntry(){}
public PDBEntry(String accessionCode, String header, Date date){
this.accessionCode = accessionCode;
this.header = header;
this.date = date;
}
}
I am still a beginner at Hibernate and using Lombok, but wouldn't this do the same thing and wouldn't Lombok automatically create the needed constructor for you?
#Entity
#Data
#SuppressWarnings("PMD.UnusedPrivateField")
public class PDBEntry implements Serializable {
#Id
#NaturalId
#NotEmpty
#NonNull
#Length(max = 4)
private String accessionCode;
#NaturalId
#NotEmpty
#NonNull
private Date date;
#NaturalId
// We allow for the header to be 'null'
private String header;
private Boolean isValidDssp;
#Temporal(TemporalType.TIMESTAMP)
private Date lastUpdated = new Date(System.currentTimeMillis());
}
Also, the original programmer of this code says he allows for the header to be 'null', yet he explicitly created a constructor that needs a value for header. Am I missing something or is this a bit contradictory?
Have a look at #NoArgsConstructor, #RequiredArgsConstructor, #AllArgsConstructor.
The constructor behavior of #Data is like #RequiredArgsConstructor:
#RequiredArgsConstructor generates a
constructor with 1 parameter for each
field that requires special handling.
All final fields get a parameter, as
well as any fields that are marked as
#NonNull that aren't initialized where
they are declared.
Given that none of your fields are either final or #NonNull, this will result in a no-argument constructor. However, this is not the most expressive way to achieve this behavior.
What you'll probably want in this case is a #NoArgsConstructor (optionally combined with a #AllArgsConstructor), to clearly communicate the intended behavior, as is also indicated in the documentation:
Certain java constructs, such as
hibernate and the Service Provider
Interface require a no-args
constructor. This annotation is useful
primarily in combination with either
#Data or one of the other constructor
generating annotations.
That bit is contradictory you're right. I've not used Lombok before but with hibernate if you want to be able to create a bean and persist you need the default constructor as given above as far I was aware. It uses Constructor.newInstance() to instantiate new objects.
Here is some hibernate documentation which goes into more detail.
Hibernate Documentation
If you are using #Data with a #NonNull field and still want a noargs-constructor, you might wanna try to add all 3 annotation together
#NoArgsConstructor
#RequiredArgsConstructor
#AllArgsConstructor
Apparently an old intelliJ bug which I did replicate in Eclipse Kepler and lombok v0.11.4
#NoArgsConstructor,
#RequiredArgsConstructor,
#AllArgsConstructor
Generate constructors that take no arguments, one argument per final / non-null field, or one argument for every field. Read this lombok-project
#Data
#RequiredArgsConstructor /*Duplicate method Someclass() in type Someclass*/
#NoArgsConstructor(access=AccessLevel.PRIVATE, force=true) /*Duplicate method Someclass() in type Someclass*/
#Entity
public class Someclass {
#Id
private String id;
private String name;
private Type type;
public static enum Type { X , Y, Z}
}
Fixed it by making member variables final
#Data
#RequiredArgsConstructor
#NoArgsConstructor(access=AccessLevel.PRIVATE, force=true)
#Entity
public class Someclass {
#Id
private final String id;
private final String name;
private final Type type;
public static enum Type { X , Y, Z}
}

Categories

Resources