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;
}
}
Related
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.
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);
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.
My spring-data-rest integration test fails for a simple json request. Consider the below jpa models
Order.java
public class Order {
#Id #GeneratedValue//
private Long id;
#ManyToOne(fetch = FetchType.LAZY)//
private Person creator;
private String type;
public Order(Person creator) {
this.creator = creator;
}
// getters and setters
}
Person.java
ic class Person {
#Id #GeneratedValue private Long id;
#Description("A person's first name") //
private String firstName;
#Description("A person's last name") //
private String lastName;
#Description("A person's siblings") //
#ManyToMany //
private List<Person> siblings = new ArrayList<Person>();
#ManyToOne //
private Person father;
#Description("Timestamp this person object was created") //
private Date created;
#JsonIgnore //
private int age;
private int height, weight;
private Gender gender;
// ... getters and setters
}
In my test I created a person by using personRepository and inited order by passing person
Person creator = new Person();
creator.setFirstName("Joe");
creator.setLastName("Keith");
created.setCreated(new Date());
created.setAge("30");
creator = personRepository.save(creator);
Order order = new Order(creator);
String orderJson = new ObjectMapper().writeValueAsString(order);
mockMvc.perform(post("/orders").content(orderJson).andDoPrint());
Order is created but creator is not associated with the order. Also I want to pass request body as a json object. In this my json object should contain creator as follows
{
"type": "1",
"creator": {
"id": 1,
"firstName": "Joe",
"lastName": "Keith",
"age": 30
}
}
If I send request body with the following json, the call works fine
{
"type": "1",
"creator": "http://localhost/people/1"
}
But I don't want to send the second json. Any idea how to solve the issue. Because already my client is consuming the server response by sending first json. Now I migrated my server to use spring-data-rest. After that all my client code is not working.
How to solve this?
You are correctly associating order with the creator, however the Person is not associated with the orders. You are missing the List<Order> orders field in Person class. Add this, add annotations, add methods for adding order to person and then before sending JSON you should call something like this:
creator.addOrder(order);
order.setCreator(cretr);
Did you try using cascade = CascadeType.ALL in #ManyToOne annotation
public class Order {
#Id #GeneratedValue//
private Long id;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)//
private Person creator;
private String type;
public Order(Person creator) {
this.creator = creator;
}
// getters and setters
}
Both your Order and Person classes should implement Serializable to properly break them down into and rebuild them from JSON.
There are some ways to solve your problem, but I want give you a hint. You just can save only "id" of your person and get the person by "id" from your database, when you need this.
It solves your problem and it also saves the memory.
I believe you need to do two things to get this work.
Handle the deserialization properly. As you expect Jackson to populate the nested Person object via the constructor you need to annotate this with #JsonCreator. See here:
http://www.cowtowncoder.com/blog/archives/2011/07/entry_457.html
One of more powerful features of Jackson is its ability to use arbitrary >constructors for creating POJO instances, by indicating constructor to use with
#JsonCreator annotation
...........................................
Property-based creators are typically used to pass one or more
obligatory parameters into constructor (either directly or via factory
method). If a property is not found from JSON, null is passed instead
(or, in case of primitives, so-called default value; 0 for ints and so
on).
See also here on why Jackson may not be able to automatically work this out.
https://stackoverflow.com/a/22013603/1356423
Update your JPA mappings. If the associated Person is now populated correctly by the Jackson deserializer then by adding the necessary JPA cascade options to the relationship then both instances should be persisted.
I think then the following should work as expected:
public class Order {
#Id
#GeneratedValue(...)
private Long id;
#ManyToOne(fetch = FetchType.LAZY, cascade = cascadeType.ALL)
private Person creator;
private String type;
#JsonCreator
public Order(#JsonProperty("creator") Person creator) {
this.creator = creator;
}
}
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}
}