I am trying to intercept the object that is being returned in my controller so that I can create a flat JSON structure of the response, before Spring invokes Jackson's serialization process.
I am going to support a query parameter that allows the client to flatten the response body. Something like:
/v1/rest/employees/{employeId}/id?flat=true
The controller method looks something like:
public Employee getEmployee(...) {}
I would like to avoid implementing this flattening logic in every one of my service calls and continue to return the Employee object.
Is there some kind of facility in Spring that would allow me to A) read the query string and B) intercept the object that is being returned as the response body?
Here's one idea. There may be a better way, but this will work:
Define an extra request mapping to do the flat mapping:
#RequestMapping(path = "/endpoint", params = {"flat"})
public String getFlatThing() {
return flatMapper.writeValueAsString(getThing());
}
// The Jackson converter will do its ordinary serialization here.
#RequestMapping(path = "/endpoint")
public Thing getFlatThing() {
return new Thing();
}
the "flatMapper" implementation can be whatever you like so long as it works.
One option is to use Jackson's ObjectMapper to write the value as json first and then use https://github.com/wnameless/json-flattener to flatten that to your desired output. There may also be a way to define a custom ObjectMapper that does flat mapping, though that would take some more work on your part.
Related
I am using a domain object imported from other service which implements builder pattern and hence have no No-arg Constructor and setters. I want to map JSON coming from #RequestBody to this domain object. I know I can annotate it with #JSONPojoBuilder and some other ones but i don't have control over it. So I'm stuck with writing my own JSON to Object mapper which i don't think is a good idea as it involves validations etc. and is tightly coupled with request body.
Code -
#RequestMapping(value = "someurl", method = RequestMethod.POST)
public ResponseEntity<ObjectA> addObject(#PathVariable String id, #RequestBody #NonNull String body) {
ObjectA obj = service.addObject(id, mapper.mapJSONToObject(body));
return new ResponseEntity<>(obj , HttpStatus.CREATED);
}
Instead of writing my own mapper, I heard we can use some message convertor and pass our Builder class as some argument. I did not find such examples, if anybody has done it before or can point me to some site, that'd be helpful.
I've never tried that, but in general Spring MVC supports the abstraction of "Custom Converters" that you can write in order to help spring to convert JSON to complicated objects.
I assume that you have a MyComplicatedClass without no-ops constructor but with a builder.
So you can do the following:
import org.springframework.core.convert.converter.Converter;
#Component // note the converter is also a spring bean so you can, say, inject object mapper and convert to JsonNode-s for example (see below)
public class MyComplicatedClassConverter implements Converter<String, MyComplicatedClass> {
#Autowired // or use constructor injection
private ObjectMapper objectMapper;
/**
* Override the convert method
* #param object in a string representation to be converted
* #return
*/
#Override
public MyComplicatedClass convert(String object) {
JsonNode node= objectMapper.readTree(/*byte array input stream from object string or something */);
return MyComplicatedClass.builder()
.withSomeProperty(node.get("someProperty").textValue()
.withAnotherProperty(node.get("anotherProperty").intValue()
.build();
}
}
Now, when your application recognizes this converter it allows using MyComplicatedClass in the controller's methods as if spring knows how to convert it.
So the code snippet that you've presented in your example should work.
All this is done without "touching" the code of MyComplicatedClass.
I have a DTO class and some REST services that sometimes return (among other things) a List of those DTOs.
I cannot alter that DTO, as it's used in several places of the project.
However, only for one specific REST service, I need to exclude some of the fields of that DTO object.
Basically I need to be able to apply this solution only at a certain point.
I tried applying #JsonFilter("restrictionFilter") to my DTO class, but then I get an error if I don't use that filter with a mapper every time I marshall the object into a JSON, like this:
final String writeValueAsString = mapper.writer(
new SimpleFilterProvider()
.addFilter("restrictionFilter",
SimpleBeanPropertyFilter.filterOutAllExcept("name", "sizeInByte"))
).writeValueAsString(objectsList);
The error is Cannot resolve PropertyFilter with id 'restrictionFilter'; no FilterProvider configured...
This issue sounds like a perfect Decorator design pattern use.
Create a new DTO with a constructor that gets the original DTO and create which get methods you want or ignore whatever get methods you like.
For example:
public class NewDto {
OldDto oldDto;
public NewDto(OldDto oldDto){
this.oldDto = oldDto;
}
public String getName(){
return oldDto.getName();
}
}
Now you will only need to return the NewDto object, like so:
return new NewDto(oldDto)
I have the following code:
#RequestMapping(value="/mobile/device", method = RequestMethod.PUT)
public ResponseEntity<Void> flagDevice (#RequestBody List<MobileDeviceData> devicedataList, #RequestHeader(value="special_code") String specialCode) {
// Implementation details
}
Each instance of MobileDeviceData that gets created needs to have a param field filled in with the RequestHeader special_code.
How would I go about doing this so that it is fully populated by the time the flagDevice method body gets called?
Thanks in advance.
This is non trivial.
An HttpMessageConverter is already provided that deserializes the JSON, that's the MappingJackson2HttpMessageConverter. It has access to request headers. You could extend that class to also use the headers for deserialization (this is extremely difficult to do generically, as opposed to only for MobileDeviceData).
You could use Spring AOP, intercept the method, retrieve the arguments, cast to the appropriate types, and assign the value yourself.
The solution I would go for is the simplest: do it yourself in the handler method. Loop the the List and use a corresponding setter to set the specialCode for each MobileDeviceData.
Another option is to define your own HandlerMethodArgumentResolver specifically for List<MobileDeviceData> parameters that need to be constructed from header vales.
I'm thinking the answer here is probably no, but just in case.
I'm doing something like this:
#RequestMapping(value="data.json", params="query=overview")
public String getOverview(#RequestBody MyRequest myRequest) {
[...]
return "overview";
}
#RequestMapping(value="data.json", params="query=detail")
public String getDetail(#RequestBody MyRequest myRequest) {
[...]
return "detail";
}
and the client is POSTing JSON data, which is deserialized by Jackson on the way in and bound to the MyRequest parameter, all working nicely.
However, I don't like the query type having to be specified in the URL of the requests. What I would like is to include the query parameter in the JSON object and use that to drive the #RequestMapping. Is this possible?
If not, I guess I will implement a single mapping and have that method delegate to others based on the incoming data, but that feels like a step in the wrong direction.
What you are trying to do does not work out of the box.
If you don't like the param why don't you add the qualifier to the URL like so:
#RequestMapping("/overview/data.json")
#RequestMapping("/detail/data.json")
If you absolutely need the functionality you mention, you could implement a custom RequestMappingHandlerMapping that would do what you want by extending that class as is done here.
It's not possible if you remove the params. You have to have something distinct between the two mappings. If you are intent on getting rid of the params, best you could do is have a single method/mapping and call your services or whatever other logic you have according to what the value of query is in your MyRequest object.
#RequestMapping(value="data.json")
public String getOverviewOrDetail(#RequestBody MyRequest myRequest) {
if (myRequest.getQuery().equalsIgnoreCase("overview")) {
[...]
return "overview"
} else if(myRequest.getQuery().equalsIgnoreCase("detail")) {
[...]
return "detail"
}
}
Since both methods are unmarshalling to the same object, you don't really need two separate methods/mappings.
I'm trying out apache-camel, and I've set up a basic route that calls an http service via http4 component, transforms the result via unmarshal().json(JsonLibrary.Jackson), and then prints out part of the response in a bean component.
The problem I'm having is that it blows up at runtime when it gets to the json unmarhsaller:
No type converter available to convert from type: java.util.HashMap to the required type: com.xxx.MyType
The response is of this format:
{"data":[{"x":"y"},{"x":"z"}]}
And my object model is like:
#lombok.Data
class Response {
private List<Elem> data;
}
#lombok.Data
class Elem {
private String x;
}
So it would appear that the unmarshaller thinks the response is a hash map, whereas I want it to unmarshal into an object structure. Is there a way to get it to do what I want?
Found the answer, posting in case anyone else runs into this. The route builder should be setup like:
from("direct:start").to("http4://...").unmarshal().json(JsonLibrary.Jackson,com.xxx.Response)
.to("bean:com.xxx.MyResponseEchoer")
I.e. pass the class type to the json method.