skip to Main Content

I’m developing APIs for an exam project, but I wanted their responses to be consistently using a wrapping class on all of them (Telegram Bot API style for those who know them).

So, for example, having these two classes:

public class User {
    public int id;
    public String name;
}

public class Item {
    public int id;
    public String itemName;
    public User owner;
}

What Spring returns to me is this output:

{
    "id": 1,
    "itemName": "theItem",
    "owner": {
        "id": 2,
        "name": "theUser"
    }
}

What I want instead is for this output to be returned:

{
    "ok": true,
    "data": {
        "id": 1,
        "itemName": "theItem",
        "owner": {
            "id": 2,
            "name": "theUser"
        }
    }
}

Maybe using a class wrapper like this:

public class ResponseWrapper<T> {
    public boolean ok;
    public T data;
}

Is it possible to do this?

2

Answers


  1. Chosen as BEST ANSWER

    I thank @JustinMathew for the help, at the end, in my case (using Spring WebFlux with Kotlin), the ResponseBodyResultHandler class was more useful to me.

    // File: /MicroserviceApplication.kt
    
    @SpringBootApplication
    class MicroserviceApplication {
        @Autowired
        lateinit var serverCodecConfigurer: ServerCodecConfigurer
    
        @Autowired
        lateinit var requestedContentTypeResolver: RequestedContentTypeResolver
    
        @Bean
        fun responseWrapper(): ResponseWrapper = ResponseWrapper(
            serverCodecConfigurer.writers, requestedContentTypeResolver
        )
    }
    
    // File: /wrapper/model/Response.kt 
    
    data class Response<T>(
        val ok: Boolean,
        val data: T?,
        val error: Error? = null
    ) {
        data class Error(
            val value: HttpStatus,
            val message: String?
        )
    }
    
    // File: /wrapper/ResponseWrapper.kt
    
    class ResponseWrapper(writers: List<HttpMessageWriter<*>>, resolver: RequestedContentTypeResolver) :
        ResponseBodyResultHandler(writers, resolver) {
    
        override fun supports(result: HandlerResult): Boolean =
            (result.returnType.resolve() == Mono::class.java)
                    || (result.returnType.resolve() == Flux::class.java)
    
        @Throws(ClassCastException::class)
        override fun handleResult(exchange: ServerWebExchange, result: HandlerResult): Mono<Void> {
            val body = when (val value = result.returnValue) {
                is Mono<*> -> value
                is Flux<*> -> value.collectList()
                else -> throw ClassCastException("The "body" should be Mono<*> or Flux<*>!")
            }
            .map { r -> Response(true, r, null) }
            .onErrorMap { e ->
                    if (e !is Response.Error)
                        Response.Error(HttpStatus.INTERNAL_SERVER_ERROR, "Internal Server Error")
                    else e
                }
            .onErrorResume { e -> Mono.just(Response(false, null, e as Response.Error)) }
    
            return writeBody(body, returnType, exchange)
        }
    
        companion object {
            @JvmStatic
            private fun methodForReturnType(): Mono<Response<Any>>? = null
    
            private val returnType: MethodParameter = MethodParameter(
                ResponseWrapper::class.java.getDeclaredMethod("methodForReturnType"), -1
            )
        }
    

    Edit: I made of this answer a library for Spring WebFlux 2.7.3 here.


    P.S. I also took a cue from this other question, which faces the same problem but with Java.


  2. I understand you need a global setting to convert all your responses into a standard one. For this you can implement ResponseBodyAdvice and have a common structure for all your api responses. Refer this link for a detailed example

    Edit: For spring-webflux you can extend ResponseBodyResultHandler and override handleResult. An example is given in this answer

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search