Home > other >  Spring WebFlux - Add a wrapping class before serialization
Spring WebFlux - Add a wrapping class before serialization

Time:02-12

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?

CodePudding user response:

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

CodePudding user response:

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: Int,
        val message: String?
    )
}
// File: /wrapper/util/PublisherExtensions.kt

fun <T> Mono<T>.toServiceResponse(): Mono<Response<T>> =
    this.map { r -> Response(true, r, null) }
        .onErrorResume { e -> Mono.just(Response(false, null, Response.Error(500, e.message))) }

fun <T> Flux<T>.toServiceResponse(): Mono<Response<List<T>>> =
    this.collectList()
        .map { r -> Response(true, r, null) }
        .onErrorResume { e -> Mono.just(Response(false, null, Response.Error(500, e.message))) }
// 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)

    override fun handleResult(exchange: ServerWebExchange, result: HandlerResult): Mono<Void> {
        val body = when (val value = result.returnValue) {
            is Mono<*> -> value.toServiceResponse()
            is Flux<*> -> value.toServiceResponse()
            else -> throw RuntimeException("The \"body\" should be Mono<*> or Flux<*>!")
        }

        return writeBody(body, result.returnTypeSource, exchange)
    }

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

  • Related