简体   繁体   中英

Modify HTTP code when Serialize skipping objects conditionally that meet a Custom Criteria with Jackson (Spring boot)

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM