https://kotlinlang.org logo
#serialization
Title
# serialization
s

smallufo

01/27/2023, 3:45 PM
Hi , is there any example about how to correctly serialize spring-data’s Page container ? It is a generic container . And here is my implementation :
Copy code
class PageSerializer<T>(private val serializer: KSerializer<T>) : KSerializer<Page<T>> {

  override val descriptor: SerialDescriptor
    get() = buildClassSerialDescriptor("Page") {
      element<Int>("totalPages")
      element<Long>("totalElements")
      element<List<T>>("contents")
    }

  override fun serialize(encoder: Encoder, value: Page<T>) {
    encoder.encodeStructure(descriptor) {
      encodeIntElement(descriptor, 0, value.totalPages)
      encodeLongElement(descriptor, 1, value.totalElements)
      encodeSerializableElement(descriptor, 2, ListSerializer(serializer), value.content)
    }
  }

  override fun deserialize(decoder: Decoder): Page<T> {
    return decoder.decodeStructure(descriptor) {
      var totalPages: Int? = null
      var totalElements: Long? = null
      var contents: List<T> = emptyList()
      loop@ while (true) {
        when (val index = decodeElementIndex(descriptor)) {
          DECODE_DONE -> break@loop
          0           -> totalPages = decodeIntElement(descriptor, 0)
          1           -> totalElements = decodeLongElement(descriptor, 1)
          2           -> contents = decodeSerializableElement(descriptor, 2, ListSerializer(serializer))
          else        -> throw SerializationException("Unexpected index $index")
        }
      }
      PageImpl(contents , Pageable.unpaged() , totalElements!!)
    }
  }
}
It seems ok , but I am not sure how to make use of it . Say I have an wrapped Page<Session> object :
Copy code
val pageSerializer: PageSerializer<Session> = PageSerializer(Session.serializer())

val sessionListFormat = Json {
  serializersModule = SerializersModule {
    polymorphic(Page::class) {
      subclass(pageSerializer)
    }
  }
  prettyPrint = true
}

sessionDao.findByUserOrderByCreatedDesc(user, Pageable.ofSize(10)).also { page: Page<Session> ->
        <http://logger.info|logger.info> {
          sessionListFormat.encodeToString(page)
        }
      }
And it throws :
Copy code
java.lang.IllegalStateException: Captured type paramerer T from generic non-reified function. Such functionality cannot be supported as T is erased, either specify serializer explicitly or make calling function inline with reified T
How to correct make use of this serializer ? thank you.
a

Adam S

01/27/2023, 4:37 PM
Copy code
sessionListFormat.encodeToString(page)
is the nice helper function, that will try to automatically get the serializer based on the generic type. If you use the explicit
encodeToString()
function, you need to pass in the serializer as the first param:
Copy code
sessionListFormat.encodeToString(pageSerializer, page)
that should work
s

smallufo

01/27/2023, 5:19 PM
Hi , I tried these combinations :
Copy code
val pageSerializer: PageSerializer<Session> = PageSerializer(Session.serializer())

val sessionListFormat = Json {
  serializersModule = SerializersModule {
  }
  prettyPrint = true
}

<http://logger.info|logger.info> { sessionListFormat.encodeToString(pageSerializer , page)
}
but it throws :
Copy code
java.lang.IllegalStateException: Captured type paramerer T from generic non-reified function. Such functionality cannot be supported as T is erased, either specify serializer explicitly or make calling function inline with reified T
I also tried :
Copy code
val sessionListFormat = Json {
  serializersModule = SerializersModule {
    polymorphic(Page::class) {
      subclass(PageImpl::class , pageSerializer as KSerializer<PageImpl<*>>)
    }
  }
  prettyPrint = true
}
but exception is the same
Captured type paramerer T from generic non-reified function. Such functionality cannot be supported as T is erased, either specify serializer explicitly or make calling function inline with reified T
e

ephemient

01/27/2023, 6:27 PM
Copy code
sessionListFormat.encodeToString(pageSerializer, page)
you'd also have an easier time writing the serializer via a surrogate,
Copy code
@Serializable
data class PageSurrogate<T>(
    val totalPages: Int,
    val totalElements: Long,
    val contents: List<T>,
)

class PageSerializer<T>(tSerializer: KSerializer<T>) : KSerializer<Page<T>> {
    private val surrogateSerializer = PageSurrogate.serializer(tSerializer)

    override val descriptor: SerialDescriptor = SerialDescriptor("PageSerializer", surrogateSerializer.descriptor)

    override fun serialize(encoder: Encoder, value: Page<T>) {
        encoder.encodeSerializableValue(
            surrogateSerializer,
            PageSurrogate(
                totalPages = page.totalPages,
                totalElements = page.totalElements,
                contents = page.contents,
            )
        )
    }

    override fun deserialize(decoder: Decoder): Page<T> {
        val surrogate = decoder.decodeSerializableValue(surrogateSerializer)
        return Page(
            surrogate.totalPages,
            surrogate.totalElements,
            surrogate.contents,
        )
    }
}
s

smallufo

01/28/2023, 3:25 AM
wow , it really works . thanks a lot ! Some minor modifications for anyone interested :
Copy code
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import org.springframework.data.domain.Page
import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.Pageable


@Serializable
data class PageSurrogate<T>(
  val totalPages: Int,
  val totalElements: Long,
  val contents: List<T>
)

class PageSerializer<T>(tSerializer: KSerializer<T>) : KSerializer<Page<T>> {
  private val surrogateSerializer = PageSurrogate.serializer(tSerializer)

  @OptIn(ExperimentalSerializationApi::class)
  override val descriptor: SerialDescriptor = SerialDescriptor("PageSerializer", surrogateSerializer.descriptor)

  override fun serialize(encoder: Encoder, value: Page<T>) {
    encoder.encodeSerializableValue(
      surrogateSerializer,
      PageSurrogate(
        totalPages = value.totalPages,
        totalElements = value.totalElements,
        contents = value.content,
      )
    )
  }

  override fun deserialize(decoder: Decoder): Page<T> {
    val surrogate = decoder.decodeSerializableValue(surrogateSerializer)
    return PageImpl(surrogate.contents, Pageable.unpaged(), surrogate.totalElements)
  }
}
85 Views