Spring JSON cast class property to other class dynamically - json

I have an (more) entity, which has a UserEntity class (creator).
Etc:
#Entity
class TestEntity {
#ManyToOne
private UserEntity creator;
}
Now, on UserEntity i have a many field which not important to use it in some request.
And i created a class (UserEntityMiniFied with just important fields) which has a Constructor with UserEntity,
Well, can i solute this question dynamiccaly with one json annotation, i mean,
i try:
#JsonView(UserEntityMinified.class)
private UserEntity creator;
but it not working.
thanks for any help.

I can say you're on the right track.
First – let's go through a simple example – serialize an object with #JsonView.
Here is our view:
public interface JSONView {
interface JSONBasicView {}
interface JSONAdvancedView extends JSONBasicView {}
}
And the UserEntity entity:
#Entity
class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonView(JSONView.JSONBasicView.class)
private int id;
#JsonView(JSONView.JSONBasicView.class)
private String name;
#JsonView(JSONView.JSONBasicView.class)
private String age;
#JsonView(JSONView.JSONAdvancedView.class)
private String country;
#JsonView(JSONView.JSONAdvancedView.class)
private String city;
public UserEntity() {
}
// getter/setter ..
}
And the TestEntity entity:
#Entity
class TestEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonView(JSONView.JSONBasicView.class)
private int id;
#JsonView(JSONView.JSONBasicView.class)
private String name;
#ManyToOne
#JsonView(JSONView.JSONBasicView.class)
private UserEntity userEntity;
public TestEntity() {
}
// getter/setter ..
}
Now let's serialize a TestEntity instance using our view:
public static void main(String[] args) {
UserEntity userEntity = new UserEntity();
userEntity.setId(1);
userEntity.setName("User Entity Name");
userEntity.setAge("33");
userEntity.setCountry("Country");
userEntity.setCity("City");
TestEntity testEntity = new TestEntity();
testEntity.setId(1);
testEntity.setName("Test Entity Name");
testEntity.setOtherTestEntity(userEntity);
ObjectMapper mapper = new ObjectMapper();
mapper.disable(MapperFeature.DEFAULT_VIEW_INCLUSION);
String basicView = mapper
.writerWithView(JSONView.JSONBasicView.class)
.writeValueAsString(testEntity);
System.out.println(basicView);
String advancedView = mapper
.writerWithView(JSONView.JSONAdvancedView.class)
.writeValueAsString(testEntity);
System.out.println(advancedView);
}
The output of this code will be:
// JSONBasicView
{
"id": 1,
"name": "Test Entity Name",
"userEntity": {
"id": 1,
"name": "User Entity Name",
"age": "33"
}
}
// JSONAdvancedView
{
"id": 1,
"name": "Test Entity Name",
"userEntity": {
"id": 1,
"name": "User Entity Name",
"age": "33",
"country": "Country",
"city": "City"
}
}
You can also check here to do more.

Related

Many-To-One : JSON parse error Cannot construct instance

I devleloping a REST API using spring boot
I have two entities with bidirectional OneToMany
Product class
public class Product {
private Long productId;
private String name;
private String description;
private List<ProductList> productList;
public Product() {
}
public Product(Long productId, String name, String description, List<ProductList> productList) {
this.productId = productId;
this.name = name;
this.description = description;
this.productList = productList;
}
public Long getProductId() {
return productId;
}
public void setProductId(Long productId) {
this.productId = productId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public List<ProductList> getProductList() {
return productList;
}
public void setProductList(List<ProductList> productList) {
this.productList = productList;
}
ProductList class
public class ProductList {
private Long productListId;
private String productListName;
private Product product;
public ProductList(Long productListId, String productListName, Product product) {
this.productListId = productListId;
this.productListName = productListName;
this.product = product;
}
public ProductList() {
}
public Long getProductListId() {
return productListId;
}
public void setProductListId(Long productListId) {
this.productListId = productListId;
}
public String getProductListName() {
return productListName;
}
public void setProductListName(String productListName) {
this.productListName = productListName;
}
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
}
ProductEntity class:
#Entity
#Data
#Table(name = "PRODUCT")
public class ProductEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private Integer productId;
#Column
private String name;
#Column
private String description;
#OneToMany(cascade = CascadeType.ALL, mappedBy="product", fetch =
FetchType.EAGER)
private List<ProductListEntity> productList;
// Getters,Setters,No-ArgCOnstructor, All-ArgConstructor
ProductListEntity class:
#Table
#Entity
#Data
public class ProductListEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long productListId;
private String productListName;
#ManyToOne(fetch = FetchType.EAGER, targetEntity = ProductEntity.class)
#JoinColumn(name = "FK_Product", referencedColumnName = "productId")
private ProductEntity product;
Service to save data:
public void addProduct(Product product) {
ProductEntity productEntity = new ProductEntity();
BeanUtils.copyProperties(product, productEntity);
productRepository.save(productEntity);
}
When i try to post I get this error:
"message": "JSON parse error: Cannot construct instance of
eteosf.hexagonal.domain.model.Product (although at least one Creator
exists): no String-argument constructor/factory method to deserialize
from String value ('string'); nested exception is
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot
construct instance of eteosf.hexagonal.domain.model.Product
(although at least one Creator exists): no String-argument
constructor/factory method to deserialize from String value
('string')\n at [Source: (PushbackInputStream); line: 9, column: 18]
(through reference chain:
eteosf.hexagonal.domain.model.Product["productList"]->java.util.ArrayList[0]->eteosf.hexagonal.domain.model.Product$ProductList["product"])",
"path": "/product"
JSON request body:
{
"productId": 0,
"name": "string",
"description": "string",
"productList": [
{
"productListId": 0,
"productListName": "string",
"product": "string"
}
]
}
in your json_request, inside of productList you are sending "product" as a "string". But Deserializer can not turn that string into a Product object. It has to be sent as object {}. You can leave that object empty - just not send it if all it does is point at itself.
You basically have made a mistake of confusing different principles - the whole bidirectional relationship is only to be applied at persistence level. Json requests are being sent at controller/view level and therefore you can't display the bidirectional nature in the same way. This is why you don't use Entities as controller params but use DTOs.
In your case just don't send the "product" field for the controller:
{
"productId": 0,
"name": "string",
"description": "string",
"productList": [
{
"productListId": 0,
"productListName": "string",
}
]
}
and just add it in the controller method right after receiving the parameter:
//the receiving controller method which got Parameter `ProductEntity product`
product.getProductList().forEach(productList -> productList.setProduct(product);
Like I said you shouldn't use entities in Controller method, and it should be a DTO class in order to avoid exactly this kind of issues

How to post ManyToOne entity using JSON format

I've two entities issue and user I would like to assign the user to an issue but when trying to do that using this JSON the user_id in the issues table is NULL.
#Data
#Entity
#Table(name = "issues")
public class Issue {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String number;
#Column
private String title;
#Column
private String description;
#Column
#Enumerated(EnumType.STRING)
private State state;
#JsonIgnore
#CreationTimestamp
#Column
private Timestamp createDate;
#JsonIgnore
#UpdateTimestamp
#Column
private Timestamp modifyDate;
#ManyToOne(targetEntity = User.class)
#JoinColumn
private User user;
public Issue() {
}
#Data
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String fullName;
#Column
private String username;
#Column
private String email;
#Column
private String password;
#Column
#Enumerated(EnumType.STRING)
private Role role;
#JsonIgnore
#CreationTimestamp
#Column
private Timestamp createDate;
#JsonIgnore
#UpdateTimestamp
#Column
private Timestamp modifyDate;
public User() {
}
public User(String fullName, String username, String email, String password, Role role) {
this.fullName = fullName;
this.username = username;
this.email = email;
this.password = password;
this.role = role;
}
Firstly I've created an user without any issues, but do not know how to manage with the issue here is the JSON I am using via postman.
{
"number": "3",
"title": "Create an working service",
"description": "The problem is that we do not have an working service.",
"state": "NEW",
"user_id": "1"
}
This part is responsible for saving the issue.
public void save(Issue issue) {
if (issue == null)
return;
Issue actual = issueRepository.findByNumber(issue.getNumber());
if (actual != null) {
actual.setNumber(issue.getNumber());
actual.setTitle(issue.getTitle());
actual.setDescription(issue.getDescription());
actual.setState(issue.getState());
actual.setUser(issue.getUser());
issueRepository.save(actual);
} else {
issueRepository.save(issue);
}
}
In the controller I just have #Valid #RequestBody Issue issue and the service saves the issue.
What you're sending as JSON is not an Issue.
First because it does not represent a database-persistent issue (and that's what the Issue class is for), but rather the JSON structure the public API expects from its clients.
Second because an Issue doesn't have any field named user_id, but your JSON does. So use a different class than Issue, which actually matches with the JSON structure that the API expects, and thus has a user_id property.
Then use this user_id to find the User by its ID using the UserRepository, and set that User into the Issue you're creating.
I would rename user_id to userId to respect the Java conventions, too.
You need to post json like this.
{
"number": "3",
"title": "Create an working service",
"description": "The problem is that we do not have an working service.",
"state": "NEW",
"user":{
"id":1
}
}
Here issue have join column of user so with user entity you have to pass userid.
Your save would be like this.
public void save(Issue issue) {
if (issue == null)
return;
Issue actual = issueRepository.findByNumber(issue.getNumber());
if (actual != null) {
actual.setNumber(issue.getNumber());
actual.setTitle(issue.getTitle());
actual.setDescription(issue.getDescription());
actual.setState(issue.getState());
actual.setUser(userRepositoy.findById(issue.getUser().getId()));
issueRepository.save(actual);
} else {
issueRepository.save(issue);
}
}
user_id key was not presented in your Item class.
As per entity mapping,
Please change your JSON to this.
{
"number": "3",
"title": "Create an working service", "description": "The problem is that we do not have an working service.",
"state": "NEW",
"user":{
"fullName":"USER",
"email":"user#gmail.com",
"username":"user"
}
}

Duplicate entry instead of using existing entity

I'm getting MySQLIntegrityConstraintViolationException while trying to add new Product. Each Product has Category which has unique value name. I'm getting this exception when I try to add new Product with already existing Category. Example below:
POST 1
{
"name" : "apple",
"categoryName" : "fruit"
}
Response
{
"name": "apple",
"categoryName": "fruit",
"kcal": null
}
Post 2:
{
"name" : "banana",
"categoryName" : "fruit"
}
Response:
{
"timestamp": 1533451793052,
"status": 500,
"error": "Internal Server Error",
"exception": "org.springframework.dao.DataIntegrityViolationException",
"message": "could not execute statement; SQL [n/a]; constraint [UK_8f25rdca1qev4kqtyrxwsx0k8]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement",
"path": "/product/add"
}
Which is obviously not what I'd expect, instead I want banana to use same category as apple.
Ok, the code, first entities and dto's
#Entity
#Table(name = "tbl_product")
public class Product implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Double kcal;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn
private Category category;
}
public class ProductDto {
private String name;
private String categoryName;
private Double kcal;
}
#Entity
#Table(name = "tbl_category")
public class Category implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true)
private String name;
public Category(){ }
public Category(String name){
this.name = name;
}
}
Post from controller
#PostMapping("/add")
public ProductDto addProduct(#Valid #RequestBody ProductDto productDto){
Product product = productRepository.save(dtoToEntityTranslator.translate(productDto));
return entityToDtoTranslator.translate(product);
}
And pretty straight-forward translators
public class DtoToEntityTranslator {
public Product translate(ProductDto productDto){
Product product = new Product();
product.setName(productDto.getName());
product.setCategory(new Category(productDto.getCategoryName()));
product.setKcal(productDto.getKcal());
return product;
}
}
public class EntityToDtoTranslator {
public ProductDto translate(Product product){
ProductDto productDto = new ProductDto();
productDto.setName(product.getName());
if(product.getCategory() != null) {
productDto.setCategoryName(product.getCategory().getName());
}
productDto.setKcal(product.getKcal());
return productDto;
}
}
Not sure if it's worth mention, my repository for Product
#Repository
public interface ProductRepository extends CrudRepository<Product, Long> {
}
The error is caused by this line
product.setCategory(new Category(productDto.getCategoryName()));
You are tolding Hibernate that this is a new Category because category name is not the Id for Category.
To solve this, you can get the Category with the provided and set to the Category.
Another way is that for existing Category, instead of sending the name to server, you can consider sending the category id.

Prevent field of JSON object to be excluded, while serializing one-to-many relationship in springboot

i have 2 classes , one for User details and other for User fitness
all i want is to exclude name field from final json object of FitnessRecord class using FitnessRecordRepository.findAll().
this is my json output now.
{
"fitness_id": 1,
"date_of_performance": "2018-04-06",
"user": {
"user_id": 1,
"name": "Hilal Kaldane",
"email": "hilalkaldane#gmail.com"
}
}
and what i want is
{
"fitness_id": 1,
"date_of_performance": "2018-04-06",
"user": {
"user_id": 1,
"email": "hilalkaldane#gmail.com"
}
}
User.class
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer user_id;
#NotNull
private String name;
#NotNull
#Column(unique = true)
private String email;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "user")
private Set<FitnessRecord> fitness_record = new HashSet<>();
//Getter and Setters are omitted
}
FitnessRecord.class
public class FitnessRecord {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer fitness_id;
public Integer getFitness_id() {
return fitness_id;
}
public void setFitness_id(Integer fitness_id) {
this.fitness_id = fitness_id;
}
#JoinColumn(name="user_id")
#ManyToOne
private User user;
#Temporal(TemporalType.DATE)
private Date date_of_performance;
}
Note:- I dont want to use JSON ignore on User Class, since i will be using the name field in other places such as user information retrieval

Can not deserialize instance of SaleListDTO out of START_ARRAY token

The issue is i'm getting this error.
I have to simulate rest service call because it's being developed now by another team.
com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of com.egencia.service.invoiceaggregator.cache.SaleListDTO out of START_ARRAY token
Here is my jackson mapper bean
Jackson2ObjectMapperBuilder
.json()
.featuresToEnable(DeserializationFeature.UNWRAP_ROOT_VALUE, DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT) .serializationInclusion(JsonInclude.Include.NON_NULL)
.serializationInclusion(JsonInclude.Include.NON_EMPTY)
.failOnUnknownProperties(false)
.build();
#JsonRootName("list")
public class SaleListDTO {
private SaleDTO[] list;
public SaleDTO[] getList() {
return list;
}
public void setList(SaleDTO[] list) {
this.list = list;
}
}
Here is the JsON file
{"list": [
{
"id": 111111,
"currency": "EUR",
"country": "ITA",
"name": "Italy",
"code": "IT"
},...
]}
I have tested so many combinations but in vain. please help
Remove #JsonRootName("list").
Here is the working example :
#Getter
#Setter
#Data
#AllArgsConstructor
#NoArgsConstructor
#ToString
public class SaleListDTO {
#JsonProperty("list")
private SaleDTO[] list;
}
#Getter
#Setter
#Data
#AllArgsConstructor
#NoArgsConstructor
#ToString
public class SaleDTO {
private int id;
private String currency;
private String country;
private String name;
private String code;
}
Test Method:
#Test
public void testConversion() throws JsonParseException, JsonMappingException, IOException{
ObjectMapper mapper=new ObjectMapper();
SaleListDTO dto=mapper.readValue(new File(PATH), SaleListDTO.class);
System.out.println(dto.toString());
}
Response :
SaleListDTO(list=[SaleDTO(id=111111, currency=EUR, country=ITA, name=Italy, code=IT), SaleDTO(id=22222, currency=IN, country=INDIA, name=CHENNAI, code=IT)])