Spring Boot and Jackson: Define which fields to serialize on external class - java

I'm working on a rest api project with spring boot and hibernate, and I'm wondering on json serialization of RestController using Jackson.
Here is the problem: I use external hibernate entities class defined in a library I cannot edit. This classes are very complex and define lot of field I'm not interested in when I return the object with the rest api.
Actually, I've solved the problem wrapping the original class with a wrapper class that exposes only the values I want to return from the controller.
Eg:
original class
class AccountEntity {
///...
public String getName() {
return this.name;
}
/// ... Lot of code here
}
Wrapper class:
class AccountWrapper {
AccountEntity original;
public AccountWrapper(AccountEntity original) {
this.original = original;
}
public String getName() {
return this.original.getName();
}
}
and the use the Wrapper as following
#RestController("/api/user")
public class UsersController {
#GetMapping("/")
public AccountWrapper getUser() {
AccountEntity account = //get account in some way
AccountWrapper accountWrapper = new AccountWrapper(account);
return accountWrapper;
}
}
The method works well, but it's not very clean and makes stuff more complex (e.g., when I have to return lists), because I always have to wrap the original class.
I didn't found a method to make me able to specify which fields I want to serialize without modify (and I cannot) the original class.
Any help?

Instead of using a wrapper class, create a DTO object for the rest API that will be leaner than the DB entity and a trasformer to create DTO from entity (and vice a verce)
The difference from using a wrapper here is that the DB entity is not part of the DTO, and thus does not need to be serialized on the response.
The big advantage here is that you separate the DB layer from the API layer, which makes it more flexible and easy to manage.
you can read more about this pattern here

Apparently, you can use Jackson Mixins to annotate a class with Jackson annotations.
See this answer for example.
The idea is to create an class with the annotations you want and to use objectMapper.getSerializationConfig().addMixInAnnotations() to register the MixIn with your class.
For example:
//Class you don't controll
public class User {
private String name;
private String password; //attribute we want to omit
//... getters and setters
}
public abstract class UserMixIn {
#JsonIgnore String getPassword();
}
objectMapper.addMixInAnnotations(User.class, UserMixIn.class);
Hope it helps,

Related

How to ignore field/column only when return response from spring boot

I need to ignore the field when return the response from spring boot. Pls find below info,
I have one pojo called Student as below
Student {
id,
name,
lastName
}
i am getting a body for as PostRequest as below
{
id:"1",
name:"Test",
lname:"Test"
}
i want get all the data from frontEnd (id,name,Lname) But i just want to return the same pojo class without id as below,
{
name:"Test",
lName:"Test"
}
I have tried #JsonIgnore for column id, But it makes the id column as null(id=null -it is coming like this even when i send data to id field from postman) when i get the data from frontEnd.
I would like to use only one pojo to get the data with proper data(withoud getting id as Null), and need to send back the data by ignoring the id column.
Is there any way to achieve it instead of using another pojo?
You just need to use #JsonInclude(JsonInclude.Include.NON_NULL) at class level and it will be helpful for ignore all your null fields.
For example :
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Test {
// Fields
// Constructors
// Getters - setters
}
As of now you are using only one POJO it's not good practice because it's your main entity into your project, so good practice is always make DTO for the same.
This is possible via the #JsonView annotation that is part of Jackson. Spring can leverage it to define the views used on the controller.
You'd define your DTO class like this:
class User {
User(String internalId, String externalId, String name) {
this.internalId = internalId;
this.externalId = externalId;
this.name = name;
}
#JsonView(User.Views.Internal.class)
String internalId;
#JsonView(User.Views.Public.class)
String externalId;
#JsonView(User.Views.Public.class)
String name;
static class Views {
static class Public {
}
static class Internal extends Public {
}
}
}
The Views internal class acts as a marker to jackson, in order to tell it which fields to include in which configuration. It does not need to be an inner class, but that makes for a shorter code snippet to paste here. Since Internal extends Public, all fields marked with Public are also included when the Internal view is selected.
You can then define a controller like this:
#RestController
class UserController {
#GetMapping("/user/internal")
#JsonView(User.Views.Internal.class)
User getPublicUser() {
return new User("internal", "external", "john");
}
#GetMapping("/user/public")
#JsonView(User.Views.Public.class)
User getPrivateUser() {
return new User("internal", "external", "john");
}
}
Since Spring is aware of the JsonView annotations, the JSON returned by the /public endpoint will contain only externalId and name, and the /internal endpoint will additionally include the internalId field.
Note that fields with no annotation will not be included if you enable any view. This behaviour can be controlled by MapperFeature.DEFAULT_VIEW_INCLUSION, which was false in the default Spring ObjectMapper when I used this for the last time.
You can also annotate your #RequestBody parameters to controller methods with JsonView, to allow/disallow certain parameters on input objects, and then use a different set of parameters for output objects.

How can I force Spring to serialize my controller's #ResponseBody as the method's declared return type?

I am attempting to use interfaces to define flexible response bodies from my Spring controllers.
What I expect: When I call an endpoint using Curl/Postman/etc, I should receive JSON objects that contain only the fields visible in the interface that the controller returns.
What I'm getting: When I call either endpoint, I receive JSON objects with every field defined in my entity.
Let's say my entity looks like this:
MyEntity.java
public class MyEntity implements ListEntityResponse, GetEntityResponse {
int dbid;
String name;
String description;
public int getDbid() { return dbid; }
public String getName() { return name; }
public String getDescription() { return description; }
}
Let's say MyEntity has many more fields that include complex data types that aren't suitable for serialization as part of a large list, or for certain other use cases. To solve this problem, I've created interfaces to limit which fields are visible in the response object. In this example, the first interface only defines two of the three getters, while the second interface defines all of them.
ListEntityResponse interface:
public interface ListEntityResponse {
int getDbid();
String getName();
}
GetEntityResponse interface:
public interface GetEntityResponse {
int getDbid();
String getName();
String getDescription();
}
And finally, here are my controllers. The important part is that each defines its return type as one of the interfaces:
ListEntityController
#GetMapping(path="/{name}")
public #ResponseBody List<ListEntityResponse> getList() {
return handler.getList(name);
}
GetEntityController
#GetMapping(path="/{name}")
public #ResponseBody GetEntityResponse getByName(#PathVariable("name") String name) {
return handler.getByName(name);
}
To recap, if we assume that our handler returns MyEntity objects, then I want that object to be serialized by Spring as the interface defined in the controller's return type. E.G. each JSON object in the list returned by the ListEntityController should have only the dbid and name fields. Unfortunately, that's not happening, and the returned JSON objects have every field available despite being masked as interface objects.
I have attempted to add #JsonSerialize(as = ListEntityResponse.class) to my first interface, and a similar annotation to the second. This works only if the entity implements just one of those interfaces. Once the entity implements multiple interfaces, each annotated with #JsonSerialize, Spring will serialize it as the first interface in the list regardless of the controller's return type.
How can I force a Spring to always serialize its Controller's responses as the controller function's return type?
Note: I am trying to find a solution that does not require me to use #JsonIgnore or #JsonIgnoreProperties. Additionally, I am trying to find a solution that does not require me to add #JsonView to my entity classes. I am willing to use the #JsonView annotation in the interfaces, but don't see a clean and maintainable way to do so.
How can I force Spring to always serialize its controller's responses as
the controller function's return type?
Please note that I am not interested in using #JsonIgnore,
#JsonIgnoreProperties, or #JsonView to provide the view masking that I
require. They do not fit my use case.
One of the options would be to create a thin wrapper over MyEntity class, which would be responsible for providing the required serialization-shape.
Every shape would be represented by its own wrapper, implemented as a single-field class. To specify serialization-shape, we can use as property of the #JsonSerialize annotation, by assigning the target interface as a value. And since we don't need the wrapper itself to reflected in the resulting JSON, we can make use of the #JsonUnwrapped annotation.
Here's a wrapper for GetEntityResponse shape:
#AllArgsConstructor
public class GetEntityResponseWrapper implements EntityWrapper {
#JsonSerialize(as = GetEntityResponse.class)
#JsonUnwrapped
private MyEntity entity;
}
And that's a wrapper for ListEntityResponse shape:
#AllArgsConstructor
public class ListEntityResponseWrapper implements EntityWrapper {
#JsonSerialize(as = ListEntityResponse.class)
#JsonUnwrapped
private MyEntity entity;
}
Basically, we have finished with serialization logic.
And you can use these lean classes in your controllers as is. But to make the solution more organized and easier to extend, I've introduced a level of abstraction. As you probably noticed both wrapper-classes are implementing EntityWrapper interface, its goal is to abstract away the concrete implementation representing shapes from the code in Controllers/Services.
public interface EntityWrapper {
enum Type { LIST_ENTITY, GET_ENTITY } // each type represents a concrete implementation
static EntityWrapper wrap(Type type, MyEntity entity) {
return switch (type) {
case LIST_ENTITY -> new ListEntityResponseWrapper(entity);
case GET_ENTITY -> new GetEntityResponseWrapper(entity);
};
}
static List<EntityWrapper> wrapAll(Type type, MyEntity... entities) {
return Arrays.stream(entities)
.map(entity -> wrap(type, entity))
.toList();
}
}
Methods EntityWrapper.wrap() and EntityWrapper.wrapAll() are uniform entry points. We can use an enum to represent the target type.
Note that EntityWrapper needs to be used in the return types in your Controller.
Here the two dummy end-points I've used for testing (I've removed the path-variables since they are not related to what I'm going to demonstrate):
#GetMapping("/a")
public List<EntityWrapper> getList() {
// your logic here
return EntityWrapper.wrapAll(
EntityWrapper.Type.LIST_ENTITY,
new MyEntity(1, "Alice", "A"),
new MyEntity(2, "Bob", "B"),
new MyEntity(3, "Carol", "C")
);
}
#GetMapping("/b")
public EntityWrapper getByName() {
// your logic here
return EntityWrapper.wrap(
EntityWrapper.Type.GET_ENTITY,
new MyEntity(2, "Bob", "B")
);
}
Response of the end-point "/a" (only two properties have been serialized):
[
{
"name": "Alice",
"dbid": 1
},
{
"name": "Bob",
"dbid": 2
},
{
"name": "Carol",
"dbid": 3
}
]
Response of the end-point "/b" (all three properties have been serialized):
{
"name": "Bob",
"description": "B",
"dbid": 2
}

Can model class implement Model UI?

So far in my Java code with Spring Boot I was using models, or POJO objects to achieve better control of my objects, etc. Usually I am creating Entities, Repositories, Services, Rest controllers, just like documentation and courses are suggesting.
Now however I am working with Thymeleaf templates, HTML a bit of Bootstrap and CSS in order to create browser interface. For methods in #Controller, as parameter, I am passing Model from Spring Model UI like this:
#GetMapping("/employees")
private String viewAllEmployees(Model employeeModel) {
employeeModel.addAttribute("listEmployees", employeeService.getAllEmployees());
return "employeeList";
}
My question is: How can I use my POJO objects instead of org.springframework.ui.Model;?
My first guess was this:
public class EmployeeModel implements Model{
private long employeeId;
private String firstName;
private String lastName;
private String email;
private String phone;
private long companyId;
//getter and setter methods
}
And in order to do that I have to #Override Model methods which is fine with me. And it looks like Java, Spring etc. does not complain in compile time, and I can use this POJO object in my #Controller like this:
#Controller
public class EmployeeController {
#Autowired
private EmployeeService employeeService;
#GetMapping("/employees")
private String viewAllEmployees(EmployeeModel employeeModel) {
employeeModel.addAttribute("listEmployees", employeeService.getAllEmployees());
return "employeeList";
}}
I run the code and it starts, shows my /home endpoint which works cool, however when I want to go to my /employees endpoing where it should show my eployees list it throws this:
Method [private java.lang.String com.bojan.thyme.thymeApp.controller.EmployeeController.viewAllEmployees(com.bojan.thyme.thymeApp.model.EmployeeModel)] with argument values:[0] [type=org.springframework.validation.support.BindingAwareModelMap] [value={}] ] with root cause java.lang.IllegalArgumentException: argument type mismatch
exception.
Please note that Rest controller is working perfectly in browser and Postman.
Is it possible that String as a method is the problem? Should my method be of some other type like List<EmployeeModel> or maybe EmployeeModel itself? If it is so, how to tell the method that I want my employeeList.html to be returned?
I sincerely hope that someone can halp me with this one :)
How can I use my POJO objects instead of org.springframework.ui.Model;?
I don't think that is the best practice when you are working with Thymeleaf. According to their documentation, you should attach your Objects to your Model. So in your controller you would be manipulating models that contain your Pojos.
Example:
#RequestMapping(value = "message", method = RequestMethod.GET)
public ModelAndView messages() {
ModelAndView mav = new ModelAndView("message/list");
mav.addObject("messages", messageRepository.findAll());
return mav;
}
You should always use org.springframework.ui.Model as argument. This class is basically a Map with key/value pairs that are made available to Thymeleaf for rendering.
Your first example is how you should do it:
#GetMapping("/employees") //<1>
private String viewAllEmployees(Model model) {
model.addAttribute("employees", employeeService.getAllEmployees()); // <2>
return "employeeList"; // <3>
}
<1> This is the URL that the view will be rendered on
<2> Add any Java object you want as attribute(s) to the model
<3> Return the name of the Thymeleaf template. In a default Spring Boot with Thymeleaf application, this will refer to the template at src/main/resources/templates/employeeList.html. In that template, you will be able to access your model value with ${employees}.

Is there an equivalent of Jackson + Spring's `#JsonView` using Quarkus + JSONB?

I'm playing with Quarkus and trying to build a CRUD REST application; I'm trying to get 2 endpoints returning 2 different views of the same entities. Here is an example on how I would have done in Spring + Jackson:
#Entity
public class Car{
public String model;
#ManyToOne( fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
public Owner owner;
// [...]
}
#Entity
public class Owner{
public String name;
// [...]
}
Here it is the important part: now if I were using Jackson I would have create a CarView class:
public class CarView {
public static class Public {};
public static class Private extends Public {};
}
And with that I would have annotated Car.model with #JsonView(CarView.Public.class) and Car.owner with #JsonView(CarView.Private.class) and then just annotate with the same annotations my methods in the REST controller in order to tell Jackson which view I want to use:
#RequestMapping("/car/{id}")
#JsonView(CarView.Public.class)
public Car getPublic(#PathVariable int id) { /*...*/ }
#RequestMapping("/car/private/{id}")
#JsonView(CarView.Private.class)
public Car getPrivate(#PathVariable int id) { /*...*/ }
Can I accomplish the same result using Quarkus & JSON-B?
Quarkus supports usage of JsonViews to manage the serialization/deserialization of request/response.
(Just to let you know, sadly it's not supported (yet) by smallry-openapi implementation, so even if the serialization would work, you'll still see the full model in swagger.)
An example of usage, taken from official guide https://quarkus.io/guides/resteasy-reactive#jsonview-support:
JAX-RS methods can be annotated with #JsonView in order to customize the serialization of the returned POJO, on a per method-basis. This is best explained with an example.
A typical use of #JsonView is to hide certain fields on certain methods. In that vein, let’s define two views:
public class Views {
public static class Public {
}
public static class Private extends Public {
}
}
Let’s assume we have the User POJO on which we want to hide some field during serialization. A simple example of this is:
public class User {
#JsonView(Views.Private.class)
public int id;
#JsonView(Views.Public.class)
public String name;
}
Depending on the JAX-RS method that returns this user, we might want to exclude the id field from serialization - for example you might want an insecure method to not expose this field. The way we can achieve that in RESTEasy Reactive is shown in the following example:
#JsonView(Views.Public.class)
#GET
#Path("/public")
public User userPublic() {
return testUser();
}
#JsonView(Views.Private.class)
#GET
#Path("/private")
public User userPrivate() {
return testUser();
}
When the result the userPublic method is serialized, the id field will not be contained in the response as the Public view does not include it. The result of userPrivate however will include the id as expected when serialized.
Have you checked #JsonbVisibility or "Jsonb adapter" part in
https://javaee.github.io/jsonb-spec/users-guide.html annotation from Jsonb? I am afraid maybe there isn't a solution in Jsonb yet like #JsonView in Jackson. Jsonb adapter is configuration at bean level(you choose the Jsonb instance when you (de)serialize), not at view level.

JSON serialization of different attributes of a single entity for different requests using Jackson

There are several REST calls that require the same JSON entity with a different set of attributes. Example of the entity:
public class JsonEntity
{
public String id;
public String name;
public String type;
public String model;
}
JsonEntity is a part of the complex responses of different calls. The first call requires the whole JsonEntity without changes. Second call requires JsonEntity without type and model attributes. Thrid one requires JsonEntity without name attribute.
Is there any way to retrieve the same JSON entity with a particular set of attributes depending on the particular context (except separating JsonEntity) using Jackson?
I see 3 ways of doing this:
1. Use #JsonGetter
This annotation tells jackson to use a metho rather than a field for serialization.
Create 3 subclasses of JsonEntity, one for each response. Change JsonEntity and use #IgnoreField on every field, make them protected if possible. On each subclasses, create specific getter for the fields you need, example:
public class JsonEntitySecondCall extends JsonEntity
{
#JsonGetter("id")
public String getId(){
return id;
}
#JsonGetter("name")
public String getName(){
return name;
}
}
Also, create a clone/copy constructor for JsonEntity. For your second call, create a new JsonEntitySecondCall by cloning the original JsonEntity, and use it in your API. Because of the anotation, the created Object will only serialisze the given fields. I don't this you can just cast your object, because Jackson uses reflection.
2. Use #AnyGetter
the AnyGetter annotaiton allows you to define a map of what will be serialized:
private Map<String, Object> properties = new HashMap<>();
#JsonAnyGetter
public Map<String, Object> properties() {
return properties;
}
Now you just need to tell your JsonEntity what properties it needs to return before each call (you could create 3 methods, one for each context, and use an enum to set which one must be used.).
3. Use #JsonInclude(Include.NON_NULL)
This annotation tells Jackson not to serialize a field if it is null. You can then clone your object and set null the fields you don't want to send. (this only works if you shouldn't send null elements to the API)
For more on Jackson annotations use this link.

Categories

Resources