I am getting started with kotlin and stuck with an issue.
I have a request function as below :
fun dumRequest(): MyRequest {
val token = JwtTokenGenerator.createToken(user)
return MyRequest(token.serialize())
}
And this is being passed as an argument to a function as :
val response: ResponseEntity<MyRequest> = callMyRequest(dumRequest())
And callMyRequest() is of generic type as :
private inline fun <reified T, reified Y> callMyRequest(request: Y): ResponseEntity<T> {
val headers = HttpHeaders()
headers.add(CONTENT_TYPE, "application/json")
headers.add(AUTHORIZATION, request.token) // I want something like this here
// other business logic
}
I want to get the token from the request object which is being pass to callMyRequest() and set it in the AUTH header. But since this is a generic type, not sure how I can get the token field out of this ?
Any help would be highly appreciated.
TIA
I don't really think you need to make Y reified here. It would suffice to restrict it to an interface containing the token, and then letting MyRequest implement that interface. Like <Y : AuthenticatedRequest>.
Actually, you don't really need the Y type parameter at all, because you could just take the interface-type as a parameter directly.
Something like this:
interface AuthenticatedRequest {
val token: String
}
data class MyRequest(override val token: String) : AuthenticatedRequest
private inline fun <reified T> callMyRequest(request: AuthenticatedRequest): ResponseEntity<T> {
val headers = HttpHeaders()
headers.add(CONTENT_TYPE, "application/json")
headers.add(AUTHORIZATION, request.token) // I want something like this here
// other business logic
}
It's when you want to deserialize your result to T that the value of reified comes in handy. It might make you able to do something like response.readEntity<T>(), without having to deal with the class-objects directly.
Related
Suppose I have an instance of the following class:
data class User(val name: String, val startedOn: LocalDate, val score: BigDecimal)
Its toString() method is provided automatically and it outputs this:
val user = User("Mike", LocalDate.of(2021, 1, 2), BigDecimal.TEN)
println(user)
User(name=Mike, startedOn=2021-01-02, score=10)
Is there another standard function to provide a compilable String for an instance of any data class:
User("Mike", LocalDate.of(2021, 1, 2), BigDecimal("10"))
Of course, I can write something myself, using reflection, but I don't want to reinvent the wheel. This will allow me to write unit tests faster.
Edit: I'm trying to quickly replace real API calls with mocks. So Id like to add something my code calling the API:
val request = getRequest(...)
println(toCompilableString(request))
val response = myApi.call(request)
println(toCompilableString(response))
and use that output in my tests like this:
val request = <output from the first println>
val response = <output from the second println>
every { myApi.call(request) } returns response
TIA!
There is no built-in way to my knowledge to do this, but there are libraries that can help, such as Kotlin Poet. However, you won't get the automatic behaviour you're looking for.
If you really want to do this with pure Kotlin, the only "built-in" way I can think of right now is to override toString(). You don't have to use reflection, but you'll have to construct the String you want by hand. For instance, something like:
data class User(val name: String, val startedOn: LocalDate, val score: BigDecimal) {
override fun toString(): String {
return """User("$name", ${startedOn.toCompilableString()}, BigDecimal("$score"))"""
}
private fun LocalDate.toCompilableString() =
"LocalDate.of($dayOfMonth, $monthValue, $year)"
}
I have a generic function to fetch/get any list out of the SharedPreferences. However, when I wanted to test, that it does not work, when I saved a list of say, Messages and ask for a list of say, Ints, it still worked. It just ignored the type I precised and returned a List of JsonObjects. When I debugged the whole code, I found, that apparently the function does not care about the inferred class type. I´ll first put here the code, so I can explain the problem:
fun <T> getListFromPreferences(preferences : SharedPreferences, key : String)
: MutableList<T> {
val listAsString = preferences.getString(key, "")
val type: Type = object : TypeToken<List<T>>() {}.type
val gson = SMSApi.gson
return gson.fromJson<ArrayList<T>>(listAsString, type)
?: ArrayList()
}
So, what I would expect, was, that when I call the function like this:
PreferenceHelper.getListFromPreferences<Message>(preferences, TEST_KEY)
the "type" variable in the above code should return List. However the result the debugger shows me is: java.util.List<? extends T>
I have absolute no idea, why the inferring does not work, but I´d really like it to work to ensure, what I am requesting is actually what I get, for obvious reasions.
Does anybody know a reason and a solution for this weird behaviour?
Due to type erasure, actual type information about T is lost, so basically this method returns List<Any?> (even if you pass Int as T).
To preserve the actual type, you need to declare this method with reified parameter:
inline fun <reified T> getListFromPreferences(preferences : SharedPreferences, key : String)
: MutableList<T> {
//...
}
I'm trying to create a Json object using Jackson but I can't use put because it's deprecated and will fail the pipelines checks in my company so I'm trying to use set:
fun HttpRequest.toJSONString(): String {
val mapper = ObjectMapper()
val root = mapper.createObjectNode()
val headers = mapper.createObjectNode()
this.headers().map().entries.forEach {h ->
headers.put(h.key, mapper.convertValue(h.value, JsonNode::class.java)) // its deprecated
}
root.set("headers", headers) // its failing
root.set("url_path", "https://facebook.com") // fails
return root.toString()
}
I got an error message: Not enough information to infer parameter T in operator fun <T : JsonNode!> set(p0: String!, p1: JsonNode!): T!
Please specify it explicitly.
How can I solve it?
I would advise you to try something more like that:
fun HttpRequest.toJSONString() =
ObjectMapper().writeValueAsString(
this.headers
.put("url_path", listOf("https://facebook.com"))
// put the rest here
)
It is usually inconvenient to deal directly with the Node objects and they are protected. I would strongly advise you to write your whole objects in kotlin and then use writeValueAsString from the ObjectMapper.
Use put when the value is a string (that version of the method is not deprecated). Use replace when the value is a JsonNode, or, if you want to chain the invocations together use set with a type parameter. Code to demonstrate:
val mapper = ObjectMapper()
val root = mapper.createObjectNode()
val headers = mapper.createObjectNode()
headers.put("key", "value") // Use put when the value is a primitive
root.replace("headers", headers) // Use replace for objects
root.set<JsonNode>("headers", headers) // Or use set with a type parameter
root.put("url_path", "https://facebook.com")
return root.toString()
Let's say I have a function:
fun doSomething(vararg pairs: Pair<String, *>) {
// Do things with the pairs
}
The problem with this approach is that it allows any type for the second half of Pair (e.g. Pair<String, CustomType1>).
What if I only want to allow a finite number of types, how would I achieve that?
If the function had a simpler signature, I could achieve the restriction via overload, like so:
fun doSomethingSimpler(param: Boolean) {
// Boolean implementation
}
fun doSomethingSimpler(param: Int) {
// Int implementation
}
// etc.
If the restricted type "set" was in my control, I could use an interface or a sealed class to achieve this. E.g.
sealed class Root
class Child1 : Root()
class Child2 : Root()
fun doSomethingICanControl(param: Root) {
// Root implementation
}
Yet what if I don't have control over the types or they are primitive, how do I prevent * from allowing everything through?
I know I could use smart-casts to get run-time safety, but can this be done at compile time?
Or does the language disallow it?
Edit 1
I know I could create my own box types (e.g. MyBoolean) and use a common interface or sealed class, but that would be boilerplate that everyone would have to write every time they needed to.
Edit 2
To be clear, I'd like to be able to make an invocation like so:
doSomething(
"key1" to false,
"key2" to "value2",
"key3" to 86
)
... I.e. Have a mixed set of "second" (of Pair) types.
So to sum it quickly up:
You want to call methods from a library that expects Pair<String, *>,
but limit the possible values that * can be.
TL;DR: What you are trying to accomplish is not possible without some kind of wrapper, because
We have no Sum-Types in Kotlin, so no way to tell the compiler that you expect an Int or a Double or a Float and nothing else
If a library-method expects something to be Pair<String, *>, there is no way for us to tell the compiler, that we just want to be able to give it a String instead of *
One way to get that behaviour is to create a Decorator (Decorator Pattern), e.g. create your own extension methods that allow only a subset
class Foo {
//Allows everything
fun doSomething(param: Pair<String, *>)
}
//Now lets create our own extension methods
fun Foo.doSomethingWithInt(param: Pair<String, Int>)
fun Foo.doSomethingWithBoolean(param: Pair<String, Boolean>)
fun Foo.doSomethingWithString(param: Pair<String, String>)
Or if you dont want to be able to call Foo.doSomething() you
can create a Decoractor-Class:
class FooDecorator {
val foo = Foo()
fun doSomething(param: Pair<String, Int>) { }
}
And the following example is not possible without some kind of Wrapper, because there are no Sum-Types in Kotlin:
doSomething(
"key1" to false,
"key2" to "value2",
"key3" to 86
)
What you could do is something like:
At first, create your own JSONItem type and add Extension-Methods to Types that can be used as one
class JSONItem<T> private constructor (item: T)
fun Int.asJSONItem() = JSONItem(this)
fun String.asJSONItem() = JSONItem(this)
fun Boolean.asJSONItem() = JSONItem(this)
Then you are able to do something like that:
//Your own personal doSomething
fun doSomething(varargs: param: Pair<String, JSONItem>) {
//Call the real doSomething()
doSomething(param.map { Pair(it.first, it.second.item) }}
}
doSomething(
"key1" to false.asJSONItem(),
"key2" to "value2".asJSONItem(),
"key3" to 86.asJSONItem()
)
Denotable union and intersection types are not currently supported in Kotlin (as of 1.1.x).
This is the relevant issue.
I have received a JavaScript object in response to a remote HTTP request. I have a kotlin model (trait) that defines the various fields I expect on the object (the nullable ones are optional).
First, I want to do an is check to make sure my object is in fact of the expected type. I initially tried payload is MyModel but that doesn't work due to the way the is operator is written in kotlin.js.
Second, I want to cast to MyModel so I can get auto-complete, etc. on the object while I work with it. Normally, the is alone would be enough but since that doesn't work I need something for this problem as well.
I would like to avoid manually populating my object from a dynamic. I wouldn't mind doing this so much if I could use by Delegates.mapVal(...) but that requires a Map<String, Any?> and I don't know how to get my dynamic/Any? payload into a Map<String, Any?>.
1) We don't have structure check for is in performance reasons.
I don't sure that we need generic solution for this, but anyway I created issue about it, feel free to vote or star it to get updates.
2) is enough if you use smart cast, like:
if (payload is MyModel) {
// call MyModel members on payload
}
But don't forget about (1) :)
3) You can write something like:
class MapDynamic<out V>(val d: dynamic) {
public fun get(thisRef: Any, desc: PropertyMetadata): V {
return d[desc.name]
}
}
class Foo(data: dynamic) {
val field: Int by MapDynamic(data)
}
fun main(args : Array<String>) {
val f = Foo(object { val field = 123 })
println(f.field)
}
But it looks too verbose, but You can add additional logic for e.g. when data don't have requested field. And if You don't need custom logic I think cast is enough.
For the second part, the cast, you can do:
fun responseHandler(payload: dynamic) {
val myModel = payload as MyModel
}
or
fun responseHandler(payload: dynamic) {
val myModel: MyModel = payload
}
This will throw an NPE if payload is null, but it won't actually validate that the payload matches MyModel. In particular, you may end up with null fields/properties that shouldn't be if the payload was missing those fields/properties.