Java - Jackson, De-Serializing subclasses - java

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.

Related

Convert from JSON Object into java records and then passed into relational database

I am requesting data using RestTemplate and get a JSON Object.
I want to insert this data into a relational database.
My thoughts after I have been reading was to convert this to java records.
It is a nested JSON Object where I want to insert the different levels into different tables.
I am building this project using Sping Boot,
I would like to insert the the following data:
id, owner, model, year
into a table namned cars
and then I want to insert the events inte an event table.
type, cost, date
into a table namned events
{
"meta": {
"total_count": 2,
"current_count": 2,
"per_page": 10,
"start": 1,
"end": 2,
"current_page": 1,
"page_count": 1
},
"data": [
{
"id": 1,
"owner": "Charles John",
"model": "Volvo,
"year": 2020
"services": {
"events": [
{
"type": "repair",
"cost": 1000
"date": "2022-01-12"
},
{
"type": "cleaning",
"cost": 200
"date": "2022-01-15"
},
{
"type": "washing",
"cost": 100
"date": "2022-03-05"
}
]
}
},
{
"id": 2,
"owner": "John Carlsson",
"model": "Mercedes,
"year": 2021
"services": {
"events": [
{
"type": "repair",
"cost": 4000
"date": "2022-02-12"
},
{
"type": "cleaning",
"cost": 200
"date": "2022-02-27"
}
]
}
}
]
}
My RestTemplate looks something similar to this:
public void getCarEvents() {
RestTemplate rt = new RestTemplate();
HttpEntity<?> request = createAuth();
String result = restTemplate.getForObject(buildUri("/cars").toString(), String.class);
ResponseEntity<CarsResponse> carsResponse = restTemplate.exchange(result, HttpMethod.GET, request, new ParameterizedTypeReference<CarsResponse> {});
List<CarsResponse> response = carsResponse.getBody();
...
...
...
}
Where CarsResponse looks something like this:
public record CarsResponse(
Meta meta;
ArrayList<Car> data
) {}
whre Car looks like this:
public record Car(
int id,
String name,
int age,
Services services
) {}
public record Services(
ArrayList<Event> events
) {}
public record Event (
String type,
int cost,
String date,
){}
Should I also make a class for Car and Event like the following?
#Entity
#Table(name = "event")
public class Event{
#Column(name = "type")
private String type;
#Column(name = "cost")
private int cost;
#Column(name = "date")
private String date;
//Getter, Setter, Constructor, toString()
}
I have only found example where they have more simple JSON Object and not found any with a nested version.
Where do I go from here. How do I only get the data from Event so I only insert this data into that table?
Also I would like to get the Id from Car inserted into the Event table to so I now for which car the event has been done for.
At first I would highly recommend into checking this question and particular it's first answer since you want to consume a REST API that produces a nested JSON. Basically, all answers are correct, but personally I found that one useful. You must create a POJO class just like this:
#Entity
#Table
#JsonIgnoreProperties(ignoreUnknown = true)
public class Car {
#Id
#SequenceGenerator(
name = "car_sequence",
sequenceName = "car_sequence",
allocationSize = 1
)
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "car_sequence"
)
private int id;
private String owner;
private String model;
private Long year;
// add getters and setters
}
The first two annotations have to do with the database stuff and transform the class as an entity and it's variables as columns for DB. The third annotation ignores any values from the JSON fields that are irrelevant from the variables you want and mention in your class. The annotations about id variable help to identify it as the primary key of your table and the rest are created for auto-increment in your db.
Of course, you must first add the JPA dependency in your project as well as the database's dependency. Also you need the Jackson Databind dependency as well.
Hope this helps!!!

Spring Data Rest and entities' IDs in relationships

I'm working on a Spring Data Rest based Spring Boot service whose data model is similar to the one in the tutorial:
https://www.baeldung.com/spring-data-rest-relationships
(In defining the entities, I'm using Lombok annotations):
#Data
#NoArgsConstructor
#Entity
#Table(name = "cale")
public class Book {
#Id
#GeneratedValue
private long id;
#Column(nullable=false)
private String title;
#ManyToOne
private Library library;
}
#Data
#NoArgsConstructor
#Entity
#Table(name = "library")
public class Library {
#Id
#GeneratedValue
private long id;
//...
}
invoking the endpoint /books I get the following json:
{
"_embedded": {
"books": [
{
"id": 22,
"title": "The title of the book",
"_links": {
"self": {
"href": "http://192.168.33.20:8080/books/22"
},
"book": {
"href": "http://192.168.33.20:8080/books/22"
},
"library": {
"href": "http://192.168.33.20:8080/books/22/library"
}
}
},
.
.
.
in HAL style, the only reference to the library linked to a given book is through an URL like http://192.168.33.20:8080/books/22/library
In order to get the id of the library associated to book 22, I have to perform a second GET call to the URL, which is inefficient in many cases.
Moreover, this makes it very hard to implement queries like "GET all books associated to the library whose id is 101".
Is there a way to let Spring Data Rest include also the id of the associated entity into the returned json? Something like:
{
"_embedded": {
"books": [
{
"id": 22,
"title": "The title of the book",
"library_id": 101,
"_links": {
.
.
.
}
},
.
.
You can create a projection and use it by default with an excerpt.
To define the projection :
#Projection(
name = "customBook",
types = { Book.class })
public interface CustomBook {
#Value("#{target.id}")
long getId();
String getTitle();
#Value("#{target.getLibrary().getId()}")
int getLibraryId();
}
And call :
http://192.168.33.20:8080/books/22?projection=customBook
To use this projection by default configure your repo :
#RepositoryRestResource(excerptProjection = CustomBook.class)
public interface BookRepository extends CrudRepository<Book, Long> {}

#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

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

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

JPA Repository doesn't load full JSON

I am working with REST API, and I am stacked cause of some weird issue.
I want to return as JSON, my request object, with all details and relations.
There is my repository:
#Repository("requestRepository")
public interface RequestRepository extends JpaRepository<Request, Integer> {
Request findByTitle(String title);
List<Request> findAll();
}
My Controller method:
enter image description here
{
#Autowired
private RequestServiceImpl requestService;
#RequestMapping("/getall")
public List<Request> findAll() {
for (Request req:requestService.findAllRequests()) {
System.out.println("Profession:" + req.getProfession().getName());
}
return requestService.findAllRequests();
}
}
Mapping:
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "profession_id")
private Profession profession;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "profession")
private List<Request> requests;
I also use #JsonManagedReference and #JsonBackReference with getters.
When I call http GET, I receive JSON that looks like:
enter image description here
[
{
"id": 1,
"title": "Hydraulik na juz!!!",
"minPayment": 200,
"maxPayment": 300,
"description": "short description",
"active": 1,
"creationDate": "2017-11-10"
},
{
"id": 2,
"title": "Potrzebny kierowca do Warszawy",
"minPayment": 100,
"maxPayment": 700,
"description": "another desc...",
"active": 1,
"creationDate": "2017-11-10"
}
]
There is no relation between profession and request in that JSON. Even in my database, I see relation between them:
enter image description here
I tried changing fetch type from LAZY to EAGER, and it didn't help. I don't know what is the reason that this JSON has no relation between profession and request

Categories