问题
We have lot of hibernate entity classes with nested relationships. I'm trying to find best way to convert given entity to equivalent json format.
I know about JsonIgnore, Jackson mixins and JsonFilters and have been experimenting with those.
Challenges we are facing are as following
Objects related to each other using OneToMany/JoinColumn or similar annotations - creates infinite recursion.
Utility or meta methods. Jackson seems to be going by getter methods and not by fields. Some of the methods are "meta" method that is not associated with any columns. Example getTotal method can be summing of values of couple of actual fields without having actual total field. Similar case for other cases like getIncomeAccounts which filters accounts based on some criteria.
Jackson Filter I wrote helps a little - it checks if the class field exists based on Jackson property name. It also checks if there annotations such as JoinColumn annotation to avoid recursion if the field exists.
Is there any way I can get metadata from hibernate and use it in my filters? Basically for given entity object, I am interested in knowing if the property Jackson wants to serialize will be mapped to a column and serialize only if there is column value associated with it. Hibernate is certainly aware of properties and column mappings.
Mixins and jsonignore options are workable, but then we depend upon individual developer remembering about putting annotations at appropriate place. Usually forgotten annotations are discovered too late when we really want to get the exported data to analyze some problem and create test case locally.
回答1:
What I usually do is map Entities to DTOs manually, or with the help of tools such as MapStruct.
This will give you maximum flexibility, with a bit of overhead at the beginning, unfortunately.
Over time however, you'll see it was worth it.
Jackson, GSON, and other serialization tools are obviously limited in what they can do out-of-the-box, and this kind of customization requires a bit too much work, imho, while also being difficult to understand and maintain.
Keep it simple.
回答2:
If you do not want to create new POJO
model for representing JSON
on REST API
level you need to prepare ORM
model before passing to Jackson
layer.
Enable HibernateXModule
You should start from enabling Hibernate module which fits the best to your Hibernate
version. It solves many problem with lazy-loadings
and internal data types.
Bidirectional Relationships
Read about options which Jackson
have for solving cycles problem during serialisation. Main annotations are:
- JsonManagedReference
- JsonBackReference
- JsonIdentityInfo
Define proper Visibility
You can define global visibility on ObjectMapper
, how to specify jackson to only use fields - preferably globally and customise it if needed for given class using JsonAutoDetect annotation.
View POJO
model
Probably for most cases you will be able to reuse POJO
model created for ORM
. In cases where customising JSON
output with annotation will be to hard you can always create custom class and map ORM
model to this one manually in extra mapping/business layer.
Customise serialisers
In case you need to handle some custom annotations or some fields in general way you can use BeanSerializerModifier and BeanPropertyWriter. It is not easy to implement but it is very powerful. See example usage here: Jackson custom serialization and deserialization.
Simple example how it could be done for bidirectional relations and visibility configuration:
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.util.Arrays;
import java.util.List;
public class JsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
Item item0 = new Item();
item0.setId(1);
item0.setItemName("Item 0");
Item item1 = new Item();
item1.setId(2);
item1.setItemName("Item 1");
List<Item> items = Arrays.asList(item0, item1);
User user = new User();
user.setId(123);
user.setName("Rick");
user.setUserItems(items);
items.forEach(item -> item.setOwner(user));
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(mapper.getSerializationConfig().getDefaultVisibilityChecker()
.withFieldVisibility(JsonAutoDetect.Visibility.ANY)
.withGetterVisibility(JsonAutoDetect.Visibility.NONE)
.withSetterVisibility(JsonAutoDetect.Visibility.NONE)
.withCreatorVisibility(JsonAutoDetect.Visibility.NONE));
System.out.println(mapper.writeValueAsString(user));
}
}
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
class User {
private int id;
private String name;
private List<Item> userItems;
// getters, setters
}
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
class Item {
private int id;
private String itemName;
private User owner;
// getters, setters
}
Above code prints:
{"id":123,"name":"Rick","userItems":[{"id":1,"itemName":"Item 0","owner":123},{"id":2,"itemName":"Item 1","owner":123}]}
来源:https://stackoverflow.com/questions/55383889/jackson-hibernate-meta-get-methods-and-serialization