I would like to generate different Json output for the same complex Java object depending on the use case.
For example check the following code:
class Employee {
private Long id;
private String name;
private EmployeeDetail detail;
private Department department;
...
}
class Department {
private Long id;
private String name;
private String address;
...
}
class EmployeeDetail {
private Long id;
private int salary;
private Date birthDate;
...
}
If I convert Employee to Json all of the fields from Employee, EmployeeDetail and Department will be present. And it is good for one use case.
However in the second use case I would like to skip Department details except the id field but keep the complete EmployeeDetail.
I know that I can add something similar #JsonView(EmployeeView.Basic.class) to the id field in the Department class and use Json views. However for cleaner code I would like to solve it inside the Employee class something like this:
class Employee {
private Long id;
private String name;
#JsonAllFields
private EmployeeDetail detail;
#JsonIdOnly
private Department department;
...
}
At the moment I use the Jackson library but can switch if required.
i think you can use com.fasterxml.jackson.annotation.
#JsonIgnore is used to ignore the logical property used in serialization and deserialization. #JsonIgnore can be used at setter, getter or field. You can use it in the fields in Department class except on ID. There are so many ways to do it. Either you can allow the only getter in serialization etc.
Example:
#JsonIgnore(false)
private String id;
OR
#JsonIgnoreProperties({ "bookName", "bookCategory" })
public class Book {
#JsonProperty("bookId")
private String id;
#JsonProperty("bookName")
private String name;
#JsonProperty("bookCategory")
private String category;
}
To know more about it, please refer: https://www.concretepage.com/jackson-api/jackson-jsonignore-jsonignoreproperties-and-jsonignoretype
I hope this helps.
Just found a solution using #JsonFilter
Now the Employee class looks like this:
class Employee {
private Long id;
private String name;
private EmployeeDetail detail;
#JsonFilter("departmentFilter")
private Department department;
...
}
And the code to generate the limited json looks like this:
ObjectMapper mapper = new ObjectMapper();
SimpleFilterProvider filterProvider = new SimpleFilterProvider();
filterProvider.addFilter("departmentFilter", SimpleBeanPropertyFilter.filterOutAllExcept("id"));
mapper.setFilterProvider(filterProvider);
One small cons is that now I also need to define the filter to generate the full, detailed json like this:
filterProvider.addFilter("departmentFilter", SimpleBeanPropertyFilter.serializeAll());
Related
I am Java web developer, usually develop Spring MVC.
I have been using #RequestMapping or #RequestParam for mapping to hashMap at Controller.
It is a terrible way. I should always cast type when using value.
But nowadays I try to use #ModelAttribute to write clean code at Controller.
However, there are some problem.
case 1) make DTO for each EndPoint.
We can make DTO for each EndPoint, but DTO will have many duplicated property.
#Getter
#Setter
#ToString
class GetUserInfoDTO {
private String id;
private String name;
}
#Getter
#Setter
#ToString
class PostUserInfoDTO {
private String name;
private Integer age;
private String address;
private String gender;
private String email;
private Date joinDate;
}
in controller,
#GetMapping("/user")
public ResultDTO getUserInfo (#ModelAttribute GetUserInfoDTO){
...
return ResultDTO;
}
#PostMapping("/user")
public ResultDTO postUserInfo (#ModelAttribute PostUserInfoDTO){
...
return ResultDTO;
}
In this case, we can apply independent validation strategy for each End-Point.
for example..
#Getter
#Setter
#ToString
class GetUserInfoDTO {
#NotNull
private String id;
private String name;
}
#Getter
#Setter
#ToString
class PostUserInfoDTO {
#NotNull
private String name;
#NotNull
private Integer age;
#NotEmpty
private String address;
private String gender;
private String email;
private Date joinDate;
}
like this.
But so many model classes made, and so many duplicated property exists.
case 2. make common DTO for each Controller.
We can make DTO for each Controller, and reuse them.
#Getter
#Setter
#ToString
class UserInfoDTO {
private String id;
private String name;
private Integer age;
private String address;
private String gender;
private String email;
private Date joinDate;
}
#GetMapping("/user")
public ResultDTO getUserInfo (#ModelAttribute UserInfoDTO){
//I want only id, name
...
return ResultDTO;
}
#PostMapping("/user")
public ResultDTO postUserInfo (#ModelAttribute UserInfoDTO){
...
return ResultDTO;
}
But In this case, we can only pass specific properties.
If someone send other parameter than id and name, we can't notice. ( 400 error not occur )
Code assistance can't recommend us specific properties that use at single end-point.
I don't like these cases.
In first case, I should make so many models and It's management will be so hard.
Second case, unnecessary properties exists and it hard to validate for each end-point.
Which way is the best?
Or Can you recommend another way for mapping request parameter to model object?
I have an entity with the following fields:
private Date dateOfBirth;
private String cityOfBirth;
private Long birthStateCodeId;
private Long birthCountryCodeId;
private Boolean isUSCitizen;
private Long citizenshipCountryCodeId;
private String address1;
private String address2;
private String addressCity;
private Long addressStateCodeId;
private Long addressCountryCodeId;
private String postalCode;
As you can see from the above snippet, I have
2 properties (birthStateCodeId, addressStateCodeId) where I use a state code from a StateCodes table, and
3 properties (birthCountryCodeId, citizenshipCountryCodeId, and addressCountryCodeId) where I use a country code from a CountryCodes table.
Using JPA (with Hibernate as persistence provider), how do I map the above 5 properties (2 state codes and 3 country codes) to the two separate tables StateCodes and CountryCodes?
You could achieve it like this:
#Entity
public class PersonIdentification {
// primary key
#Id // and other annotations, see JPA Spec or tutorials
private long id;
// regular attributes
private Date dateOfBirth;
private String cityOfBirth;
private Boolean isUSCitizen;
private String address1;
private String address2;
private String addressCity;
private String postalCode;
#ManyToOne
private StateCode birthStateCode;
#ManyToOne
private StateCode addressStateCode;
#ManyToOne
private CountryCode birthCountryCode;
#ManyToOne
private CountryCode addressCountryCode;
#ManyToOne
private CountryCode citizenshipCountryCode;
// setter & getter methods as needed...
}
Next, define entity classes for both "Code" types as such:
#Entity
public class StateCode {
// primary key
#Id // and other annotations, see JPA Spec or tutorials
private long id;
private String code;
private String stateName;
// other attributes of interest
// setter & getter methods as needed...
}
#Entity
public class CountryCode {
// primary key
#Id // and other annotations, see JPA Spec or tutorials
private long id;
private String code;
private String countryName;
// other attributes of interest
// setter & getter methods as needed...
}
To reduce CnP code (as with the generic aspect of primary key handling (#Id) you can check this answer. It gives you detailed hints on how handle such cases more efficiently by introducing an AbstractEntity via the #MappedSuperClass annotation.
Hope it helps
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 have a composite object that I wish to store in mongodb (using spring annotations). The object is as follows:
#Document(collection="person")
class Person {
#Id
private String id;
private Address address;
private String name;
}
and the composite class Address:
#Document
class Address {
#Indexed
private Long countryId;
private String street;
#Indexed
private String city
}
I need both country and city to be indexed as part of the person collection. Alas, no index is created for them. Any ideas how to create the index?
I have tried the following which works but is not elegant:
#Document(collection="person")
#CompoundIndexes({
#CompoundIndex(name = "countryId", def = "{'address.countryId': 1}")
})
class Person {
You can set up multiple secondary indexes, if you wish. This would be a good place to start.
I already searched for this particular issue, the closest thread i found was this one: Java/JAXB: Unmarshall XML elements with same name but different attribute values to different class members But it's still not exactly what i need, so i hope someone can help me with this.
I am doing a SOAP request on a Zimbra Collaboration Suite 7 Server to get a contact. The response is something like this:
<cn fileAsStr="Arthur, Spooner" f="" id="280" rev="1973" d="1338524233000" t="" md="1338524233" ms="1973" l="7"><meta/><a n="homePostalCode">93849</a><a n="lastName">Spooner</a><a n="birthday">1980-05-24</a><a n="homeStreet">Berkleystreet 99</a><a n="firstName">Arthur</a></cn>
I want to map this to a Java object, something like this:
public class Contact {
Integer id;
Integer rev;
String namePrefix;
String firstName;
String middleName;
String lastName;
String jobTitle;
ArrayList<Adress> adresses;
Date birthday;
String department;
Integer mobilePhone;
String email;
String company;
String notes;
...
I usually do this using JAXB, but as all the elements are called a and all the attributes n, I don't know how to map this. I really would appreciate a code snippet or any kind of help. Thanks in advance.
You could try doing something like this:
#XmlAccessorType(XmlAccessType.FIELD)
public class ContactAttribute {
#XmlAttribute(name="n")
private String attribute;
#XmlValue
private String value;
}
#XmlRootElement(name = "cn")
#XmlAccessorType(XmlAccessType.FIELD)
public class Contact {
#XmlAttribute
Integer id;
#XmlAttribute
Integer rev;
//...
#XmlElements(#XmlElement(name = "a"))
List<ContactAttribute> attributes;
//...
}
Use the Castor Mapping
it will help you to Marshall and Unmarshall the data.