Filter REST response using @JsonFilter: ...

Filter REST response using @JsonFilter: Kotlin + Spring boot + Jackson + @JsonFilter

Sep 21, 2022

While I was creating a new RESTful API which serves internal and external users, I came across this problem where I have to exclude some of the fields from data classes when returning the response for external users. Before complicating this problem with usage of Spring Filters or Interceptors that can be applied to http request. I thought, why not make use of Jackson filters. Here we go, how I solved this problem

Suppose, I have a data class dogs:

data class DogDto(
    val id: UUID,
    val name: String,
    val internal-field1: String,
    val internal-field2: String,
)

And my controller method is something like this:

import au.com.bh4r4th.doggo.dog.DogService
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RestController
import java.util.*

@RestController
class DogController(
    private val dogService: DogService
) {

    @GetMapping("/v1/Kennels/{id}/dogs")
    fun retrieveDogsByKennelId(@PathVariable("id") id: UUID): List<DogDto> {
        dogService.retrieveDogs(id)
    }

}

But when I hit this endpoint I would get response with internal fields:

[
    {
        "id": <uuid>,
        "name": "great doggy",
        "internal-field1": <some value>,
        "internal-field2": <some other value>
    }
]

But I don't want to include these internal-field1 and internal-field2 in the responses. Here is how I solve this using @JsonFilter from Jackson

Step 1: Apply filter name on DogDto

import com.fasterxml.jackson.annotation.JsonFilter

@JsonFilter("myDogFilter")
data class DogDto(
    val id: UUID,
    val name: String,
    val internal-field1: String,
    val internal-field2: String,
)

Step 2: Create a utility function, so that I can re-use this filter logic again for other controllers if needed

import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider
import org.springframework.http.converter.json.MappingJacksonValue

fun <T: Any>  filterResponse(responseToFilter: T, filterName: String, excludeFields: Set<String>): MappingJacksonValue {
        val filterProvider = SimpleFilterProvider()
        filterProvider.setFailOnUnknownId(false)
        filterProvider.addFilter(filterName, SimpleBeanPropertyFilter.serializeAllExcept(excludeFields))

        val filteredResponse = MappingJacksonValue(responseToFilter)
        filteredResponse.filters = filterProvider

        return filteredResponse
}

Step 3: Change the controller logic

import au.com.bh4r4th.doggo.dog.DogService
import au.com.bh4r4th.doggo.utils.filterResponse
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RestController
import org.springframework.http.converter.json.MappingJacksonValue
import java.util.*

@RestController
class DogController(
    private val dogService: DogService
) {

    @GetMapping("/v1/Kennels/{id}/dogs")
    fun retrieveDogsByKennelId(@PathVariable("id") id: UUID): MappingJacksonValue {
        val dogs = dogService.retrieveDogs(id)
        return filterResponse(
                responseToFilter = dogs,
                filterName = "myDogFilter",
                excludeFields = setOf( "internal-field1", "internal-field2")
        )
    }

}

Now, when I request this endpoint with valid kennel id. Then, I would get a desired filtered response like this:

[
    {
        "id": <uuid>,
        "name": "great doggy"
    }
]

TADA !!! That's an easy filter to filter out the response without changing a DTO or adding heavy Spring Filter or Interceptor logic. I hope this helps.

¿Te gusta esta publicación?

Comprar bh4r4th un café

Más de bh4r4th