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.
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.
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}]}
Related
I have entities that represent my database - User, Recipe and Tag.
For data manipulation I use DTO. So UserDTO, RecipeDTO, TagDTO. When I define a relationship between entities, I use its basic User, Recipe, Tag form, but when I define these relationships in a DTO class, I use its DTO form.
For example:
DTO Class looks like this
public class UserDTO{
private String name;
private String email
private List<RecipeDTO>
}
public class RecipeDTO{
private String title;
private String description;
private UserDTO user;
}
I know how to map a DTO to an entity so that I can perform operations (CRUD) on the data in the database.
private Recipe convertToEntity(RecipeDTO recipeDTO){
Recipe recipe = new Recipe();
recipe.setTitle(recipeDTO.getTitle);
recipe.setDescription(recipeDTO.getDescription);
}
But the RecipeDTO also has a UserDTO in it, which I also need to map to an entity. How do I do this?
So I am trying to achieve a mapping inside the mapping .... (??)
I can think of the following solution.
Create method that converts UserDTO to User:
private User convertUser(UserDTO userDTO){
User user = new User();
user.setName(userDTO.getName());
user.setEmail(userDTO.getEmail());
}
And then use it while mapping RecipeDTO to Recipe.
private Recipe convertToEntity(RecipeDTO recipeDTO){
Recipe recipe = new Recipe();
recipe.setTitle(recipeDTO.getTitle());
recipe.setDescription(recipeDTO.getDescription());
//Convert UserDTO
recipe.setUser(convertUser(recipeDTO.getUser()));
}
I'm not sure if this is the right solution, as there will be more and more mappings as the code gets bigger.
The approach you described is not wrong and will work, but doing it that way will indeed involve a lot of hard work.
The way this is usually done in the industry is by letting a library do that work for you.
The two most popular mapping libraries for java are:
https://mapstruct.org/ (which uses annotation processing at compile time and auto-generates basically the same mapping code as in your example)
and
http://modelmapper.org/ (which uses black magic and reflection)
They are both easy to setup/learn and either will do the job (including mapping nested objects as in your example), so take a look at the “getting started“ section and pick the one you find more intuitive to use.
My personal recommendation would be to pick Mapstruct, as it has way fewer gotchas, generates clean human-readable code and avoids using reflection.
This may be a simple task, but I couldn't find a way to do it. Basically, I need to disallow some parameters at the time of using #RequestBody annotation in my controller.
Here is my model:
#Data
public class MyModel {
private int id;
private String name;
}
What I want to do is at the time of response, I want both of the properties to be serialized to JSON, but at the time of create or update, I prefer not to receive id as part of #RequestBody deserialization.
Right now, if I pass id in the JSON body, Spring initializes a MyModel object with its id set to the passed value.
Reason? The ID cannot be generated until the model is created, so the app shouldn't allow the ID to be set. On update, the ID needs to be passed in the URL itself e.g. (PUT /mymodels/43). This helps following the REST principles appropriately.
So, is there any way to achieve this functionality?
Update 1:
Right now, I am stuck with using a request wrapper. I created a new class MyModelRequestWrapper with only name as its property, and have used it with the #RequestBody annotation.
How you do this depends on what version of Jackson you are using. It's basically possible by a combination of the annotations #JsonIgnore and #JsonProperty on relevant fields/getters/setters.
Have a look at the answers here: Only using #JsonIgnore during serialization, but not deserialization
Jersey is not showing a list in the JSON output when I retrieve the object using Hibernate. The list within the object is defined like this:
#OneToMany(cascade=CascadeType.ALL)
#OrderColumn
private List<Project> projects = new ArrayList<Project>();
When I retrieve the object (which also contains the projects list), I get the normal fields (ints and Strings and such), but not this list. When I use the debugger, I can see that the list is indeed there, but Jersey doesn't output it in JSON.
It looks like you need to configure a JSON Serializer such as Jackson. The answers to this question have some guidance on how to do that.
Once you have Jackson with JAXB support configured, you will need to add appropriate JAXB annotations to the Project class (either XML based one or JSON based ones, the serializer can be configured to support either or both). So, for example adding this to Project
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "")
#XmlRootElement(name = "project"))
public class Project {
Should be enough to serialize Project and it's fields to JSON.
I am trying to reuse my existing EmployeeRepository code (see below) in two different microservices to store data in two different collections (in the same database).
#Document(collection = "employee")
public interface EmployeeRepository extends MongoRepository<Employee, String>
Is it possible to modify #Document(collection = "employee") to accept runtime parameters? For e.g. something like #Document(collection = ${COLLECTION_NAME}).
Would you recommend this approach or should I create a new Repository?
This is a really old thread, but I will add some better information here in case someone else finds this discussion, because things are a bit more flexible than what the accepted answer claims.
You can use an expression for the collection name because spel is an acceptable way to resolve the collection name. For example, if you have a property in your application.properties file like this:
mongo.collection.name = my_docs
And if you create a spring bean for this property in your configuration class like this:
#Bean("myDocumentCollection")
public String mongoCollectionName(#Value("${mongo.collection.name}") final String collectionName) {
return collectionName
}
Then you can use that as the collection name for a persistence document model like this:
#Document(collection = "#{#myDocumentCollection}")
public class SomeModel {
#Id
private String id;
// other members and accessors/mutators
// omitted for brevity
}
It shouldn't be possible, the documentation states that the collection field should be collection name, therefore not an expression:
http://docs.spring.io/spring-data/data-mongodb/docs/current/api/org/springframework/data/mongodb/core/mapping/Document.html
As far as your other question is concerned - even if passing an expression was possible, I would recommend creating a new repository class - code duplication would not be bad and also your microservices may need to perform different queries and the single repository class approach would force you to keep query methods for all microservices within the same interface, which isn't very clean.
Take a look at this video, they list some very interesting approaches: http://www.infoq.com/presentations/Micro-Services
I used #environment.getProperty() to read from my application.yml. Like so :
application.yml:
mongodb:
collections:
dwr-suffix: dwr
Model:
#Document("Log-#{#environment.getProperty('mongodb.collections.dwr-suffix')}")
public class Log {
#Id
String logId;
...
I've got the following models in Play Framework.
#Entity
public class Parent extends Model {
#Id
public Long id;
public String name;
public List<Child> children = new ArrayList<Child>();
}
#Entity
public class Child extends Model {
// Entity 2
#Id
public Long id
public String name;
#ManyToOne
public Parent parent;
}
Executing the following query gives me more information than I need.
toJson(Child.find.all());
As an example, I get all children as well as their parents and parent's attributes and any other adjoining information.
I've tried setting the fetch=FetchType.LAZY, but it doesn't make a difference.
Can anyone help?
Jackson's toJson() method always fetches all data while serializing Ebean's objects, so it can be real performance killer, that was discussed my proposition is using some dedicated object (not stored in DB) and filling it only with required data from 'original' object.
Check other answer, which describes this approach.
Ebean has built in JSON support and you can use that if you want exact control over the parts of the object graph to include in JSON:
JsonContext jsonContext = Ebean.json();
JsonWriteOptions options = JsonWriteOptions.parsePath("id,name");
String json = jsonContext.toJson(list2, options);
And alternatively you can apply PathProperties to both the query and json like:
PathProperties pathProperties = PathProperties.parse("id,name");
query.apply(pathProperties);
// fetch only id and name
List<App> list3 = query.findList();
// json output only id and name
JsonWriteOptions options2 = new JsonWriteOptions();
options2.setPathProperties(pathProperties);
String json2 = jsonContext.toJson(list3, options2);
Note that Ebean 4.3 had it's json support refactored to use Jackon core for parsing and generation (so it's using Jackson under the hood). You are using Play so currently stuck on an old version of Ebean so instead of using query.apply() you'd use pathProperties.apply(query) I think.
Regarding #JsonIgnore ... obviously different use cases require different JSON which is why this feature exists in Ebean (when you get a use case that needs to include parent like)
PathProperties pathProperties = PathProperties.parse("id,name,parent(id,name)");
...
I simply opted for the #JsonIgnore annotation.