So i was able to Serialize skipping objects conditionally that meet a Custom Criteria with Jackson following provided link. link: https://www.baeldung.com/jackson-serialize-field-custom-criteria
NOTE: i was able to do it without the filter.
So what i'm doing basically, checking for an attribute, and if it wasn't set, i skip returning the whole object. so what i'm getting (basing on the tutorial example):
HTTP: 200
PATH: /users
RESPONSE:
[
{
"name":"john"
},
{
"name":"adam",
"address":{
"city":"ny",
"country":"usa"
}
}
]
But if we came to a case to return a single user, who is hidden, we will face this:
HTTP: 200
PATH: /users/tom
RESPONSE: /**empty response**/
In this particular case, i want to return a HTTP response with 404 error code, not 200 like the jackson is behaving. when debugging, Jackson is serializing after the controller, so i couldn't intercept it.
I was thinking of implementing interceptors, which could intercept the jackson response writer and then if empty, return a 404 error code.
...I'm out of ideas, and leak of experience. :/
does anyone knows how to do this?
EDIT::20200323
following @Tomoki_Sato answer, i have found a solution. After trying his answer, it didn't work first. After investigating, the issue was with type mismatch.
In my controller, i always return ResponseEntity<?>
which doesn't implement the Hideable
class.
So my solution was like that, supporting ResponseEntity<<? implements Hideable>>
ResponseEntity<<? implements Hideable>>
&& <? implements Hideable>
<? implements Hideable>
responses:
@RestControllerAdvice
public class MyResponseBodyAdvice implements ResponseBodyAdvice<Hideable> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
//if returnType is <? implements hideable>
if (Hideable.class.isAssignableFrom(returnType.getParameterType()) && MappingJackson2HttpMessageConverter.class.isAssignableFrom(converterType)) {
return true;
}
//if returnType is ResponseEntity<<? implements hideable>>
List<Type> actualTypeArguments = Lists.newArrayList(((ParameterizedType) returnType.getGenericParameterType()).getActualTypeArguments());
if (actualTypeArguments.isEmpty()) {
return false;
}
try {
Class<?> responseClass = Class.forName(actualTypeArguments.get(0).getTypeName());
return Hideable.class.isAssignableFrom(responseClass) && MappingJackson2HttpMessageConverter.class.isAssignableFrom(converterType);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return false;
}
@Override
public Hideable beforeBodyWrite(
Hideable hideable, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response
) {
if (hideable == null || hideable.isRemoved()) {
response.setStatusCode(HttpStatus.NOT_FOUND);
return null;
}
return hideable;
}
}
Talking about efficiency, i didn't tested it out, ad i believe we have to test it over many types like ResponseEntity<List<? implements Hideable>>
ResponseEntity<List<? implements Hideable>>
, ResponseEntity<Set<? implements Hideable>>
ResponseEntity<Set<? implements Hideable>>
....
In Theory, i believe that @RestControllerAdvice
don't interfere here, and the JSON serializer is taking the lead converting the response... i don't know.
I hope this helps someone else :)
You can customize the response before Jackson writes it by implementing ResponseBodyAdvice
.
If you want to set the 404
HTTP status code when a user
is null or hidden
, your ResponseBodyAdvice
implementation will be something like this:
@ControllerAdvice
public class MyResponseBodyAdvice implements ResponseBodyAdvice<Hidable> {
/**
* Supports `? extends Hidable`, `ResponseEntity<? extends Hidable>` and
* `HttpEntity<? extends Hidable>` handled by
* `MappingJackson2HttpMessageConverter`
*/
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
if (!MappingJackson2HttpMessageConverter.class.isAssignableFrom(converterType)) {
return false;
}
Class<?> parameterType = returnType.getParameterType();
// if returnType is <? extends Hidable>
if (Hidable.class.isAssignableFrom(parameterType)) {
return true;
}
// if returnType is ResponseEntity<? extends Hidable> or HttpEntity<? extends
// Hidable>
if (HttpEntity.class.isAssignableFrom(parameterType)) {
Type[] actualTypeArguments = ((ParameterizedType) returnType.getGenericParameterType())
.getActualTypeArguments();
if (actualTypeArguments == null || actualTypeArguments.length != 1) {
return false;
}
try {
return Hidable.class.isAssignableFrom(Class.forName(actualTypeArguments[0].getTypeName()));
} catch (ClassNotFoundException e) {
// e.g. returnType is ResponseEntity<List<Hideable>>
e.printStackTrace();
}
}
return false;
}
@Override
public Hidable beforeBodyWrite(Hidable hidable, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
if (hidable == null || hidable.isHidden()) {
response.setStatusCode(HttpStatus.NOT_FOUND);
return null;
}
return hidable;
}
}
EDIT::20200324
I improved my answer based on the code snippet in EDIT::20200323 above so that the ResponseBodyAdvice
can support not only Hidable
but also ResponseEntity
and HttpEntity
.
I'd like to suggest that you check whether HttpEntity
(the super class of ResponseEntity
) is assignable from parameterType
so that you can prevent your ResponseBodyAdvice
from supporting unexpected parameter types like List<Hidable>
. If the ResponseBodyAdvice
supports List<Hidable>
, ClassCastException
occurs at beforeBodyWrite
.
See Also
Spring Framework Documentation - Web on Servlet Stack - 1.1.6. Interception
Java doc of ResponseBodyAdvice
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.