Jackson serialization issue. Only first object of the same entity serializes well - java

I develop a REST voting system where users can vote on restaurants. I have a Vote class which contains User, Restaurant and Date.
public class Vote extends AbstractBaseEntity {
#NotNull
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
#NotNull
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "restaurant_id")
private Restaurant restaurant;
#Column(name = "date", nullable = false)
#NotNull
private LocalDate date;
}
I need to find all votes of the day. And if there are several votes for one restaurant, only first object serializes well. The other ones shows restaurant ID instead of Restaurant object as shown below:
[
{
"id": 100019,
"user": null,
"restaurant": {
"id": 100004,
"name": "KFC"
},
"date": "2020-08-28"
},
{
"id": 100020,
"user": null,
"restaurant": 100004,
"date": "2020-08-28"
},
{
"id": 100021,
"user": null,
"restaurant": {
"id": 100005,
"name": "Burger King"
},
"date": "2020-08-28"
},
{
"id": 100022,
"user": null,
"restaurant": 100005,
"date": "2020-08-28"
}
]
So first Vote for KFC shows full restaurant info, but second shows only ID. Same for Burger King which is next 2 votes.
What could be a problem?

You need to use com.fasterxml.jackson.annotation.JsonIdentityInfo annotation and declare it for Restaurant class:
#JsonIdentityInfo(generator = ObjectIdGenerators.None.class)
class Restaurant {
private int id;
...
}
See also:
Jackson/Hibernate, meta get methods and serialisation
Jackson JSON - Using #JsonIdentityReference to always serialise a POJO by id

Related

Hibernate maps full relational object only in first occurance in the aggregation

Problem I have stumbbled upon is the relation below in my RestApi.
While requesting for all payments Object having PaymentCategory in relation is fully returned. But when PaymentCategory in the List is present for the next time, then the Payment object contains only an Id of PaymentCategory.
snippet Payment.class
#ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.EAGER)
private PaymentCategory paymentCategory;
PaymentCategory.class
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public class PaymentCategory {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
Response querying all Payments
[
{
"id": 1,
"name": "Hipoteka",
"description": "Hipoteka w banku PEKAO SA",
"paymentDate": "2022-10-11",
"lastPaymentDate": "2022-09-11",
"paymentDueDay": 11,
"amount": 13.13,
"paymentClosed": false,
"paymentClosingDate": null,
"paymentCategory": {
"id": 1,
"name": "Dom"
},
"logo": "WAPBouvlSP6gEKLEPjl/7Hmul8o=",
"repayments": []
},
{
"id": 2,
"name": "Spotify",
"description": "Spotify premium",
"paymentDate": "2022-10-25",
"lastPaymentDate": "2022-09-25",
"paymentDueDay": 25,
"amount": 29.99,
"paymentClosed": false,
"paymentClosingDate": null,
** "paymentCategory": {
"id": 2,
"name": "Rozrywka"
},**
"logo": "WAPBouvlSP6gEKLEPjl/7Hmul8o=",
"repayments": []
},
{
"id": 3,
"name": "Viaplay",
"description": "Viaplay abonament",
"paymentDate": "2022-10-11",
"lastPaymentDate": "2022-09-17",
"paymentDueDay": 17,
"amount": 34.0,
"paymentClosed": false,
"paymentClosingDate": null,
**"paymentCategory": 2,**
"logo": "WAPBouvlSP6gEKLEPjl/7Hmul8o=",
"repayments": []
}
]
What am I missing here?
Is there an additional configuration for entityManager?
Best Regards,
Mateusz
I have tried looking in some guides, but I didn't find solution I've expected.
#JsonIdentityInfo in PaymentCategory was actually limiting the object property.
There is no use in this class due to due to single directional relation.

#JsonIgnore just for some specific endpoints

I have been struggling to solve this issue on my project: Is possible to use the annotation #JsonIgnore only when endpoint has an specific value?
For example, i want to use the annotation when endpoint.equals("xxxxxxxxx"), but not use when endpoint.equals("yyyyyy").
There are 3 classes with these relationship annotations:
Client
#OneToMany(mappedBy = "ownerOfTheProduct")
#JsonIgnore
private List<Product> ownProducts = new ArrayList<>();
Category
#JsonIgnore
#OneToMany(mappedBy = "category")
private List<Product> products;
Product
#ManyToOne
#JoinTable(name = "PRODUCT_CATEGORY", joinColumns = #JoinColumn(name = "product_id"), inverseJoinColumns = #JoinColumn(name = "category_id"))
private Category category;
#ManyToOne
#JoinTable(name = "CLIENT_PRODUCT", joinColumns = #JoinColumn(name = "product_id"), inverseJoinColumns = #JoinColumn(name = "client_id"))
private Client ownerOfTheProduct;
The point is:
If i dont put the #JsonIgnore, i get a StackOverflow error, the json gets into looping and wont stop.
"id": 1,
"name": "Product name",
"price": 20.0,
"category": {
"id": 1,
"name": "Cleaning",
"products": [
{
"id": 1,
"name": "Product name",
"price": 20.0,
"category": {
...
When i mapped in a different way, and put the #JsonIgnore into the both classes: Client and Product, it works, the loopings were not more hapenning. However, when i have to use other endpoint, which the fields products and ownerOfTheProduct need to show up through api, it doesnt work cuz the #JsonIgnore is annotated.
LOOPING SOLVED
{
"id": 1,
"name": "Product name",
"price": 20.0,
"category": {
"id": 1,
"name": "Cleaning"
},
"ownOfTheProduct": {
"id": 1,
"name": "Edited",
"cpf": "Edited",
"email": "test",
"password": "test"
}
}
OTHER ENDPOINTS ARE NOT WORKING
{
"id": 1,
"name": "Edited",
"cpf": "Edited",
"email": "test",
"password": "test"
}
I'd like the field that i have mapped with #JsonIgnore (ownProducts) shows up in this request exactly this way:
{
"id": 1,
"name": "Edited",
"cpf": "Edited",
"email": "test",
"password": "test"
"ownProducts" [
{
"id": 1,
"name": "Product name",
"price": 20.0,
"category": {
"id": 1,
"name": "Cleaning"
},
]
}
Is there a way to change this? Summing up, i just want to use #JsonIgnore with especific especific endpoints, not every single endpoint on my API.
I hope yall got my question, anyway here is the link of the repository on github: https://github.com/reness0/spring-restapi-ecommerce
You cant use only #JsonIgnore but you can use #JsonView and #JsonIdentityInfo annotations from com.fasterxml.jackson.core
How it works:
You need define class with interfaces. For example:
public class SomeView {
public interface id {}
public interface CoreData extends id {}
public interface FullData extends CoreData {}
}
Mark entity fields with #JsonView(<some interface.class>)
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonView(SomeView.id.class)
private Long id;
#Column(nullable = false)
#JsonView(SomeView.CoreData.class)
private String username;
#Column(nullable = false)
#JsonView(SomeView.FullData.class)
private String email;
}
Annotate endpoint with #JsonView(<some interface.class>)
#GetMapping()
#JsonView(SomeView.FullData.class)
public User getUser() {
return <get user entity somwhere>
}
In case #JsonView(SomeView.id.class) you will get this JSON:
{
id: <some id>
}
In case #JsonView(SomeView.CoreData.class):
{
id: <some id>,
username: <some username>
}
In case #JsonView(SomeView.FullData.class):
{
id: <some id>,
username: <some username>,
email: <some email>
}
#JsonView also works with embeded objects and you can annotate one field with multiply views classes - #JsonView({SomeView.FullData.class, SomeOtherView.OtherData.class})
About Cycleing JSON. Annotate your entity class with
#JsonIdentityInfo(
property = "id",
generator = ObjectIdGenerators.PropertyGenerator.class
)
Every time when JSON serialization go in circles object data will be replaced with object id or orher field of entity for your choose.
Or as alternative you can just use DTO classes
While this is not possible to achieve using the annotation based approach (annotations make it static), you can achieve the same using any data mapper library. Create a filter based on the attribute from API. Orika library can be used: https://www.baeldung.com/orika-mapping

I am using ManyToOne , OneToMany and have endless loop when getting data

I am using ManyToOne and OneToMany in hibernate .I want to create a user who has locations.
When I get data in postman I have endless loop because when I get user it's showing a user's location and in location showing user and so on. Here is code:
Locations class :
#ManyToOne(fetch = FetchType.LAZY,cascade = CascadeType.ALL)
#JoinColumn(name=FLD_LOC, nullable=false)
private Consumer consumers;
public Consumption(String location, float consumpiton,Consumer consumer) {
this.location = location;
this.consumpiton = consumpiton;
this.consumers=consumer;
}
User class :
#OneToMany(mappedBy = Consumption.FLD_LOC,orphanRemoval = true)
private List<Consumption> locations ;
public Consumer(String clientId, String name,String location, float pwConsumption, String email, String password, String roles) {
super(clientId, name, email, password, roles);
this.locations=new ArrayList<>();
this.location=location;
this.pwcons=pwConsumption;
}
But in database it's storing name of location in users table and id of user in locations table
Here is problem looks like :
"id": 2,
"version": 1,
"updated": "2020-06-28T15:41:49.082",
"clientId": "admin",
"name": "admin",
"email": "admin123#gmail.com",
"password": "$2a$10$hgcTSHjGpxEPg6WNb0U7ouHR5J5YYR5l1XVAejdK8JsG9w2Bko00a",
"active": true,
"roles": "ROLE_ADMIN",
"locations": [
{
"locationsid": 1,
"location": "Pecs",
"consumpiton": 0.0,
"consumers": {
"id": 2,
"version": 1,
"updated": "2020-06-28T15:41:49.082",
"clientId": "admin",
"name": "admin",
"email": "admin123#gmail.com",
"password": "$2a$10$hgcTSHjGpxEPg6WNb0U7ouHR5J5YYR5l1XVAejdK8JsG9w2Bko00a",
"active": true,
"roles": "ROLE_ADMIN",
"locations": [
{
"locationsid": 1,
"location": "Pecs",
"consumpiton": 0.0,
"consumers": {
"id": 2,
"version": 1,
"updated": "2020-06-28T15:41:49.082",
"clientId": "admin",
"name": "admin",
"email": "admin123#gmail.com",
"password": "$2a$10$hgcTSHjGpxEPg6WNb0U7ouHR5J5YYR5l1XVAejdK8JsG9w2Bko00a",
"active": true,
"roles": "ROLE_ADMIN",
"locations": [
{
"locationsid": 1,
"location": "Pecs",
How to let it show in JSON Locations part only name of location or id?
Problem
This is generic issue when you have to serialise objects with bidirectional relationship.
Solution
Signal the serialiser where to stop when facing bidirectional relationship
First approach is to create custom DTOs and return them from your rest controller. In the DTOs, you will populate the location field of the customerDto with locationDtos but you will NOT set the customer field of locationDto and it will be null.
Second approach is less preferred. But we can tell the Jackson library to not to serialise it recursively by adding #JsonManagedReference and #JsonBackReference.
Replace
#OneToMany(mappedBy = Consumption.FLD_LOC,orphanRemoval = true)
private List<Consumption> locations ;
with
#OneToMany(mappedBy = Consumption.FLD_LOC,orphanRemoval = true)
#JsonManagedReference
private List<Consumption> locations ;
Replace
#ManyToOne(fetch = FetchType.LAZY,cascade = CascadeType.ALL)
#JoinColumn(name=FLD_LOC, nullable=false)
private Consumer consumers;
with
#ManyToOne(fetch = FetchType.LAZY,cascade = CascadeType.ALL)
#JoinColumn(name=FLD_LOC, nullable=false)
#JsonBackReference
private Consumer consumers;
Note:
In production systems, we don't expose all the fields of domain objects as it can have many internal fields which should not be exposed to outside. It is the reason, first approach is preferred

Spring JPA Repository ony fetches id instead of full object when it's already in result

In a SpringBoot rest application, I have two classes as follows:
User.java
and Message.java.
Message has -from- field (User) and also -to- is of type (User).
So I've made it like this:
In User.java:
#Entity
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class,
property="id")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String firstName;
private String lastName;
private String email;
#JsonIgnore
#OneToMany(mappedBy = "to")
private List<Message> receivedMessages;
#OneToOne
#JoinColumn(name = "type")
private UserType type;
In Message.java:
#Entity
public class Message {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Integer id;
#ManyToOne
#JoinColumn(name = "from_user_id")
private User from;
#ManyToOne
#JoinColumn(name = "to_user_id")
private User to;
private String subject;
private String message;
private Date sentTime;
private Date readTime;
private Integer replyTo;
(setters & getters, etc)
And apparently it works!
-BUT- let's say I have 3 messages, and the first two of them went sent to the same user, only the first of those two comes with the full user object and the seconds only it's id, as follows:
[
{
"id": 16,
"from": {
"id": 1,
"firstName": "Ale",
"lastName": null,
"email": "axfeea#gmail.com",
"username": null,
"password": "123456",
"avatar": "https://..............jpg",
"type": null
},
"to": 1,
"subject": "sub",
"message": "hola",
"sentTime": null,
"readTime": null,
"replyTo": null
},
{
"id": 17,
"from": {
"id": 2,
"firstName": "Carlos",
"lastName": "Perez",
"email": "efefe#fefe.com",
"username": null,
"password": "fe",
"avatar": "https://..................jpg",
"type": null
},
"to": 1,
"subject": "sub1",
"message": "chau",
"sentTime": null,
"readTime": null,
"replyTo": null
},
{
"id": 18,
"from": 2,
"to": 1,
"subject": "efefae",
"message": "oooook",
"sentTime": 1503249653000,
"readTime": null,
"replyTo": null
}
]
And if 3rd message comes with a non-repeated user it comes with the full object.
I need the full object to come always.
And -btw- in the database they all look good and same way.
Any ideas?
Thank you all in advance!
Since you have specified the annotation JsonIdentityInfo, Jackson serializes the objects as in the resulting JSON.
The Javadoc of the annotation specifies:
In practice this is done by serializing the first instance as full object and object identity, and other references to the object as reference values.
So if you don't want that behaviour, remove the annotation.

Java - Jackson, De-Serializing subclasses

Lets assume I have the following JPA entities:
#MappedSuperclass
public abstract class BaseForumPersistable {
#Id
Long id;
String title;
Date creationDate;
#ManyToOne
User user;
//getters, setter
}
#Entity
public class ThematicArea() {
#OneToMany(mappedBy="thematicArea")
List<Topic> topics;
//getters, setters
}
public class Topic() {
String status;
boolean isSticky;
#ManyToOne
ThematicArea thematicArea;
#OneToMany
List<Post> posts
//getters, setters
}
I also use these entities for my REST Controllers that handle POST requests. So for instance for Topic I have an /api/topics endpoint. When I send something like this as a JSON object
{
"user": {
"id": 3,
"role": "Admin"
},
"thematicArea": {
"id": 1
},
"title": "asdf",
"status": "Active"
}
It fails to create the Thematic Area, although it perfectly creates the User entity. The controller signature is as follows:
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> create(#RequestBody final Topic entity)
So when I use the debugger with a breakpoint, the ThematicArea entity is not even de-serialized.
Moreover if I send an object like this:
{
"user": {
"id": 3,
"role": "Admin"
},
"thematicArea": {
"id": 1,
"title": "topic_title"
},
"title": "asdf",
"status": "Active"
}
Which now also includes a title field in the ThematicArea object the two title fields get mixed up. This leads me to believe that it's an issue of de-serializing. Any ideas how i can fix this.

Categories