Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kotlinx.serialization not used for serializing Page<T> and ignores all configuration #28389

Open
Nek-12 opened this issue Apr 27, 2022 · 3 comments
Assignees
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: blocked An issue that's blocked on an external project change theme: kotlin An issue related to Kotlin support type: enhancement A general enhancement

Comments

@Nek-12
Copy link

Nek-12 commented Apr 27, 2022

Affects:
3.0.0-M1

When returning object from a controller method using @ResponseBody, and if the returned type is Page<T> where T is a class marked as @kotlinx.serialization.Serializable, kotlinx.serialization is not used, it is unclear what exactly is used, and, assuming it's Jackson that is used, all configuration, all annotations and other properties are ignored.

I'm using custom InstantSerializer that serializes dates as strings for Kotlinx.serialization.

My POJO is annotated as follows

@file:UseSerializers(InstantSerializer::class, UUIDSerializer::class)

@kotlinx.serialization.Serializable
data class GetProductResponse(
    /* ... */
    @JsonSerialize(using = JacksonInstantSerializer::class)
    @JsonFormat(shape = JsonFormat.Shape.STRING)
    val createdAt: Instant,
    val id: UUID,
)

Returning List or of this pojo returns string for createdAt field: "createdAt": "2022-03-29T20:19:30.304149Z"
However, returning Page the field is : "createdAt": 1650109008.203314000. This breaks FE and introduces inconsistency.

I must note that I specifically would like to use strings to serialize dates.

I tried to work around this issue by providing custom Jackson configuration:

  • Using code:
  @Bean
  fun jacksonObjectMapper(): ObjectMapper = ObjectMapper().apply {
      registerModule(JavaTimeModule())
      disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
  }
  • Using application.properties:
spring.mvc.converters.preferred-json-mapper=kotlinx.serialization
spring.jackson.serialization.write_dates_as_timestamps=false
  • Using annotations:
 @JsonSerialize(using = JacksonInstantSerializer::class)
    @JsonFormat(shape = JsonFormat.Shape.STRING)
    val createdAt: Instant,

None of the above methods worked, and I'm unable to achieve the desired behavior.

My Controller method is as follows:

@GetMapping("/")
    fun getAll(pageable: Pageable, @RequestBody request: GetProductsFilteredRequest?): Page<GetProductResponse> {
        return productRepository.findFiltered(
            pageable,
           /* ... */
        ).map { it.toResponse() }
    }

Condition evaluations report is as follows:

   JacksonAutoConfiguration.JacksonObjectMapperConfiguration#jacksonObjectMapper:
      Did not match:
         - @ConditionalOnMissingBean (types: com.fasterxml.jackson.databind.ObjectMapper; SearchStrategy: all) found beans of type 'com.fasterxml.jackson.databind.ObjectMapper' jacksonObjectMapper (OnBeanCondition)

   JacksonHttpMessageConvertersConfiguration.MappingJackson2HttpMessageConverterConfiguration:
      Did not match:
         - @ConditionalOnProperty (spring.mvc.converters.preferred-json-mapper=jackson) found different value in property 'spring.mvc.converters.preferred-json-mapper' (OnPropertyCondition)
      Matched:
         - @ConditionalOnClass found required class 'com.fasterxml.jackson.databind.ObjectMapper' (OnClassCondition)

   JacksonHttpMessageConvertersConfiguration.MappingJackson2XmlHttpMessageConverterConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 'com.fasterxml.jackson.dataformat.xml.XmlMapper' (OnClassCondition)

UPDATE:
Registering kotlin module for ObjectMapper:

@Bean
    fun jacksonObjectMapper(): ObjectMapper = ObjectMapper().apply {
        registerModule(JavaTimeModule())
        registerKotlinModule()
        disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
    }

Allows to apply a workaround by using Jackson configuration alongside kotlinx.serialization configuration (only for annotation, other properties are still ignored)

The bug is still valid because kotlinx.serialization is not used for Page content.

@sdeleuze
Copy link
Contributor

sdeleuze commented Jan 3, 2023

For some reasons, kotlinx.serialization does not detect Page<T> as a collection of T like it does for List<T>, despite the fact that Page<T> extends Iterable<T>. I have asked more details in Kotlin/kotlinx.serialization#2060 (comment) so let see Kotlin team feedback.

@sdeleuze sdeleuze added the status: blocked An issue that's blocked on an external project change label Jan 3, 2023
@Nek-12
Copy link
Author

Nek-12 commented Jan 4, 2023

To me it seems like a reasonable thing.
Your class may implement Iterable but there's no reason it should always be represented as a collection in a json.

@sdeleuze
Copy link
Contributor

Indeed see Kotlin/kotlinx.serialization#2060 (comment) related comment.

We could refine kotlinx.serialization support by replacing serializer = SerializersKt.serializerOrNull(type); by serializer = SerializersKt.serializerOrNull(this.format.getSerializersModule(), type); but we would still be blocked by the fact that PolymorphicSerializer has higher priority than ContextualSerializer, and I am not confortable implementing a custom logic in Spring because this seems a common need (Ktor has the same). I have described my ask in Kotlin/kotlinx.serialization#2060 (comment).

@sdeleuze sdeleuze removed the status: waiting-for-triage An issue we've not yet triaged or decided on label Jan 30, 2023
@sdeleuze sdeleuze added this to the 6.0.x milestone Jan 30, 2023
@sdeleuze sdeleuze modified the milestones: 6.0.x, 6.1.x Mar 6, 2023
@snicoll snicoll modified the milestones: 6.1.x, General Backlog Oct 24, 2023
@jhoeller jhoeller added in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement labels Jan 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: blocked An issue that's blocked on an external project change theme: kotlin An issue related to Kotlin support type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

6 participants