6

I'm trying to write a nice Kotlin wrapper for a web framework against kotlin 1.0.3. In that I am trying to mixin a function to the request to have it return a bean via a JSON transformation using jackson.

So in my library I have the following

private val mapper: ObjectMapper = ObjectMapper().registerModule(KotlinModule())
fun <T : Any> Request.asDataBean(type: KClass<T>): T = mapper.readValue(this.body(), type.java)

But when I goto use the code as such

post("/hello", { req, res ->
    val bean = req.asDataBean(TestBean::class)
})

It errors saying that the expected value of bean is Any. What I want is for my API to work as above where whatever the generic "class" definition that is passed into asDataBean method is the type of value that is returned back.

I've also tried

fun <T> Request.asDataBean(type: KClass<*>): T = mapper.readValue(this.body(), type.java) as T

as well as changing the usage code to

val bean: TestBean = req.asDataBean(TestBean::class)

in hopes of making it work but they also give the exact same error when using the code.

How do I get it to use the generic defined by the class type passed in as the parameter (very similar to how all the spring api's work in java)?

Ryba
  • 1,249
  • 10
  • 17
  • It is strange, the first snippet works for me all right. Could you please provide a minimal self-contained example without external dependencies? – voddan Jul 11 '16 at 12:47
  • I'm not sure I understand. I've created a github repo to show the code at https://github.com/sepatel/kotlin-seed. It is the entire project and hopefully will help describe the issue better? The error it gives is a compile error on the use of the API saying "e: Main.kt: (23, 9): Expected a value of type Any e: Main.kt: (23, 34): Type inference failed: Not enough information to infer parameter T in fun Request.asData(type: KClass<*>): T" – Ryba Jul 11 '16 at 15:45

2 Answers2

6

post function in your example requires route: (Request, Response) -> Any parameter, that is a function, taking request and response and returning some non-null value.

When you use a lambda expression as a route, its return type is inferred from the last expression of lambda's body, and since in Kotlin an assignment is not an expression, the following lambda doesn't have the return type at all:

{ req, res ->
    val bean = req.asDataBean(TestBean::class)
}

To make it work just make bean the last expression

{ req, res ->
    val bean = req.asDataBean(TestBean::class)
    bean
}

or do not use the assignment at all:

{ req, res -> req.asDataBean(TestBean::class) }

Note: I have used the following definition of asDataBean function:

fun <T: Any> Request.asDataBean(type: KClass<T>): T =
    mapper.readValue(this.body(), type.java)

Also you could make a reified overload, which calls non-reified one, so that you don't have to expose all the internals:

inline fun <reified T: Any> Request.asDataBean(): T = 
    asDataBean(T::class)

req.asDataBean<TestBean>() // usage
Ilya
  • 21,871
  • 8
  • 73
  • 92
  • Thanks. I can't believe that I mis-interpreted the error message like that. After reading your reason I felt like a newbie. Thanks for the tip on reified, I never thought about that and that sounds like the best solution to the problem at hand. – Ryba Jul 12 '16 at 13:11
5

The more Kotlin idiomatic way would be to use reified type parameters:

inline fun <reified T : Any> Request.asDataBean(): T = mapper.readValue(this.body(), T::class.java)

Which can then be consumed as:

post("/hello", { req, res ->
    val bean = req.bodyAs<TestBean>()
    res.body("Ok")
})
miensol
  • 39,733
  • 7
  • 116
  • 112
  • 1
    In order to inline you have to have all variables publicly available and in this case the mapper is privately defined in the library and so inline doesn't work. Plus I already tried to do it the way you mentioned by making the mapper public and making it reified and it still was not consumable as you described giving a compiler error of Any type expected. – Ryba Jul 11 '16 at 15:41
  • Created a branch https://github.com/sepatel/kotlin-seed/tree/reified to demonstrate that even as reified it doesn't work giving the "Expected value to be of type Any" error when used. – Ryba Jul 11 '16 at 15:59
  • @Ryba the `post("/hello", {res,req-> ...` lamba parameters must return `Any`. The assignment in Kotlin is not an expression, hence it has no value. You need to specify the return value. – miensol Jul 11 '16 at 20:54