Use jackson to wrap subclass attributes into a sub-key - java

My REST service needs to return messages of format:
{
"code": 1000,
"message": "Success",
"description": null,
"data": [
{
"custId": "00000023401443",
"engName": "MISTER NORWAYTEST'S PASSPORT",
}
]
}
where the first tier of the JSON message is basically like a message header to provide technical details about message delivery while the "data" key contains the actual business information.
If I were to create a class to reflect this, I would by default come out with something that looks like this:
public class ResponseModel<T> implements Serializable{
private Integer code;
private String message;
private String description;
#JsonProperty(value = "data")
private T dataObj;
}
but doing it this way causes my controllers to all return the same object with no real business context value to the class name and reduces readability:
#GetMapping("/profile/{userId}")
public ResponseEntity<ResponseModel> getProfile(#PathVariable String userId) {
...
}
What I would like to do is to use ResponseModel as a superclass and then inherit them into subclasses with real business context names (e.g. Customer or Account). But in order to adhere to the required JSON format, I need to ensure that attributes of the subclass as wrapped into the "data" key.
Is there a way that I can do that? Using #JsonRootName would also wrap the superclass properties as well.

The problem of your the format you want is right here :
"data": *[* <---- HERE THAT BRACKET
If you want a bracket that means your data is a list so you need to fix it by making dataObj a list :
#JsonProperty(value = "data")
private List<T> dataObj;
Now i don't see the point of subclassing ResponseModel you could just do the following :
public ResponseEntity<ResponseModel> myControllerMethod(){
List<Account> list = myService.readAccounts();
return new ResponseModel<Account>(list);//default code OK,...
}
If you make your Business classes inherits ResponseModel which is only a wrapper to handle controller's result, you will be mixing your business layer with controller's specific layer which is not a good idea.
but doing it this way causes my controllers to all return the same object with no real business context value to the class name and reduces readability:
In a Java's controller, you're suppose to have very very few lines, basically, call a service, check the answer/handle exceptions, return the response. So I don't see any problem of readability because if this. If you talk about the generic returned value, just name properly your methods.

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
}

Spring Webflux - WebClient: How to get a List<TheNestedPojo> nested inside a larger Response directly?

Small question regarding Spring Webflux, and how to get the nested List of Pojo that is present in a http response directly.
We are consuming an API which response is something like
{
"noNeedThisField": "I do not need this",
"listOfWhatIwant": [
{
"personName": "Alice",
"personAge": "11"
},
{
"personName": "Bob",
"personAge": "22"
},
{
"personName": "Charlie",
"personAge": "33"
}
],
"uselessField": "This is useless",
"manyFieldsNoNeed": "it is one response, which contains a lot of fields that I do not need, I just need to retrieve the list DIRECTLY please",
"noNeed": true,
"anotherNotImportant": "this is not important at all"
}
Basically, it is one response, which contains a lot of fields I do not need, plus an element of type list in it, which I would like to get directly.
If I create two different classes, first one
public class PojoWithListAndOtherNoNeedFields {
private String noNeedThisField;
private List<MyNestedPojo> listOfWhatIwant;
private String uselessField;
private String manyFieldsNoNeed;
private boolean noNeed;
private String anotherNotImportant;
}
//getters setters
second one
public class MyNestedPojo {
private String personName;
private String personAge;
//getters setters
}
And invokes Webclient like this:
public Mono<PojoWithListAndOtherNoNeedFields> sendReqest() {
return webClient.mutate().baseUrl("url").build().post().uri("/route").retrieve().bodyToMono(PojoWithListAndOtherNoNeedFields.class);
}
It is working fine! I just need to carry a very large class that I do not need in my code, and retrieve the nested list of what I need with a getter each time.
However, I was wondering is it is possible to do something similar as (this is not working)
public Mono<List<MyNestedPojo>> sendReqest() {
return webClient.mutate().baseUrl("url").build().post().uri("/route").retrieve().bodyToMono(List<MyNestedPojo>.class);
}
In order to retrieve the nested element directly.
My goal is to get rid of PojoWithListAndOtherNoNeedFields entirely, and getting the List< MyNestedPojo> directly. Is it possible?
How to perform this in a proper way in Spring using the Webclient please?
Thank you
You can use the #JsonIgnoreProperties annotation to inform the ObjectMapper to ignore any fields not included in your POJO when deserialisating from json to a POJO.
#JsonIgnoreProperties(ignoreUnknown = true)
public class PojoWithListAndOtherNoNeedFields {
private List<MyNestedPojo> listOfWhatIwant;
}
public class MyNestedPojo {
private String personName;
private String personAge;
}
JavaDocs

Switch field names between the received rest response and output to client

I have the following object and its value is set via a REST call as follows.
#Getter
#Setter
public class Benefit {
#JsonProperty("text")
private String headerText; // To note, I can't modify this headerText name
}
Data set from a rest call.
ResponseEntity<Benefit> response =
template.exchange(url, HttpMethod.POST, request, Benefit.class);
Benefit benefit = response.getBody();
The return value from the rest call is in following format which is why I annotated it as text.
{
"text" : "some text"
}
After this, using this response, I am passing it down as a value to the client that called me.
But when I send the information down, I don't want to name it as text.
I want to call it as description. Thus my response will be as follows:
{
"description" : "some text"
}
Queries/ Pointers
1. Is there a way to do this without me having to manually set it?
2. This headerText is in use for different REST call. In that scenario, I need to both
receive the value as text and also return as text. (Thus that has no issues).
3. Preferably any possible solutions, should not affect above point 2.
4. But is ok if it will affect. I will go with an entirely new Benefit2 Object to resolve this if there is a solution which affects point 2.
One possible way to do this is to set the value to another variable and pass that down as follows only for the particular rest call.
But finding it very cumbersome as follows.
Add a new field called description
#Getter
#Setter
public class Benefit {
#JsonProperty("text")
private String headerText;
// add a new field
private String description;
}
After the rest call, do the following:
Benefit benefit = response.getBody();
benefit.setDescription(benefit.getHeadlineText);
benefit.setHeaderText(null);
Any better ways?
To clarify on the flow:
Client calls my service
My service calls another service and got back:
{
"text" : "some text"
}
I then return the following back to the client.
{
"description" : "some text"
}
Thoughts after discussion.
Intention to use this object in both places, when calling rest and when returning response to client.
#Getter
#Setter
public class TestBenefit extends Benefit {
#Getter(AccessLevel.NONE)
#JsonProperty("text")
private String text;
private String description;
public void setText(String text) {
this.description = text;
}
}
Over time I learned that trying to use one object for multiple purposes in these scenarios is more trouble than it is worth. You should create objects that cater to your requests and responses appropriately. Use base classes if necessary. Also, I wouldn't call it Benefit2. :o) Name your classes, to some degree, for what they are used for. You could do something like...
class BenefitForOtherPurpose extends Benefit {
#JsonProperty('description')
public String getHeaderText() {
return this.headerText;
}
}
To that end, I don't think there is a way using the Jackson API to adjust the #JsonProperty value dynamically short of some reflection kung-fu that, again, is likely more trouble than it is worth. And there's nothing I know of in the Jackson API to conditionally set that outside of this complex solution:
Conditional JsonProperty using Jackson with Spring Boot

Denormalizing nested JSON object hierarchies in Jackson

Let's say I have a JSON object hierarchy like the following:
{
"name": "Mosquito Laser",
"configurations": [{
"currency": "USD",
"price": "10.00" /* the Basic option */
}, {
"currency": "USD",
"price": "50.00" /* the Pro option */
}, ]
}
I would like to deserialize this json into a java object, and flatten it into a single level. So for example, I would like to map the above json into the following java class:
#JsonIgnoreProperties(ignoreUnknown = true)
public class Product {
#JsonProperty
protected String name;
protected String lowestPrice;
protected String highestPrice;
}
I would like to use a custom method to compute the lowestPrice and highestPrice fields from the list of configurations in the json. Assume for the sake of argument that the json hierarchy and the java object have been simplified here for clarity, and that in reality they are actually much more complicated so I do not wish to implement a completely custom deserializer. I want most of the fields to be automatically deserialized using Jackson's databinding defaults, but I want to specify custom operations for certain fields.
Is there an easy way to tell Jackson to use a special method to compute the value of the lowestPrice and highestPrice fields automatically?
Use:
#JsonProperty("configuration")
#JsonDeserialize(using = ConfigurationDeserializer.class)
protected String cheapestPrice;
And a deserializer looks like this:
public class ConfigurationDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext) throws IOException {
(your logic to go from the configuration JSON to cheapestPrice goes here)
}
}
In ETL and SQL, this is aggregation. A couple of questions:
Do you need the values being aggregated?
Do you need other values from lower level JSON?
Is memory a concern?
Is CPU a concern?
Comments:
Aggregation requires saving state when parsing tree-like structures
Fast streaming parsers don't save state
State can either be passed into children or can be returned back from children.
Jackson can be slow when parsing unneeded child values.
If all you need is to modify values just define getter method(s):
public class Product {
public String name;
protected String lowestPrice;
protected String highestPrice;
public int getLowestPrices() {
return calculateLowest(lowestPrice);
}
// and similarly for highestPrice...
}
or, when reading JSON in, define matching setter. Methods have precedence over fields.

Categories

Resources