I have a project which deals with some ORM mapping of objects (there are some #OneToMany relations etc).
I am using REST interface to treat these objects and Spring JPA to manage them in the API.
This is an example of one of my POJOs:
#Entity
public class Flight {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
private String dateOfDeparture;
private double distance;
private double price;
private int seats;
#ManyToOne(fetch = FetchType.EAGER)
private Destination fromDestination;
#ManyToOne(fetch = FetchType.EAGER)
private Destination toDestination;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "flight")
private List<Reservation> reservations;
}
When making a request, I have to specify everything in the JSON:
{
"id": 0,
"reservations": [
{}
],
"name": "string",
"dateOfDeparture": "string",
"distance": 0,
"price": 0,
"seats": 0,
"from": {
"id": 0,
"name": "string"
},
"to": {
"id": 0,
"name": "string"
}
}
What I would prefer, is actually specifying the id of referenced object instead of their whole bodies, like this:
{
"id": 0,
"reservations": [
{}
],
"name": "string",
"dateOfDeparture": "string",
"distance": 0,
"price": 0,
"seats": 0,
"from": 1,
"to": 2
}
Is that even possible? Could someone give me some insight on how to do this? I am only finding tutorials on how to do the opposite (the solution I already have).
Yes, it is possible.
For this purpose you should use pair of Jackson annotations to your entity model:
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
#JsonIdentityReference(alwaysAsId = true)
protected Location from;
Your serialized JSON will look instead of this:
{
"from": {
"id": 3,
"description": "New-York"
}
}
like this:
{
"from": 3
}
As mentioned in official documentation:
#JsonIdentityReference - optional annotation that can be used for
customizing details of a reference to Objects for which "Object
Identity" is enabled (see JsonIdentityInfo)
alwaysAsId = true used as marker to indicate whether all referenced
values are to be serialized as ids (true);
Note that if value of 'true' is used, deserialization may require additional contextual information, and possibly using a custom id
resolver - the default handling may not be sufficient.
You can only ignore your JSON content using #JsonIgnore annotation.
The field which you want to hide in your JSON at there you can annotate that with #JsonIgnore.
You can change your JSON like this :
{
"id": 0,
"reservations": [
{}
],
"name": "string",
"dateOfDeparture": "string",
"distance": 0,
"price": 0,
"seats": 0,
"from": {
"id": 0
},
"to": {
"id": 0
}
}
But You can't like this:
{
"id": 0,
"reservations": [
{}
],
"name": "string",
"dateOfDeparture": "string",
"distance": 0,
"price": 0,
"seats": 0,
"from": 0,
"to": 1
}
Related
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.
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 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
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
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.