Kotlin Generics, correct syntax for type parameters - kotlin

I have the following class, which basically gets a JSON string from AWS, then converts it to an instance of a data class...
class SecretsManager(region: String) {
private val gson = Gson()
private val smClient = AWSSecretsManagerClientBuilder.standard().withRegion(region).build()
fun <T> getSecret(id: String): T {
val req = GetSecretValueRequest().withSecretId(id)
val json = smClient.getSecretValue(req).getSecretString()
return gson.fromJson(json, T::class.java)
}
}
To be used like this...
val myInstance = SecretsManager("eu-west-2").getSecret<MyDataClass>("myId")
Currently, I get an error - Cannot use 'T' as reified type parameter. I can get around this by marking the function as inline and T as reified , but then I can't access the private attributes from within the function.
What's the best way to do this in Kotlin?

You need to add another parameter to the getSecret method, and also need to add an inline reified method for that to work. See the code below
class SecretsManager(region: String) {
private val gson = Gson()
private val smClient = AWSSecretsManagerClientBuilder.standard().withRegion(region).build()
fun <T> getSecret(type: Class<T>, id: String): T {
val req = GetSecretValueRequest().withSecretId(id)
val json = smClient.getSecretValue(req).getSecretString()
return gson.fromJson(json, type)
}
inline fun <reified T> getSecret(id: String): T = getSecret(T::class.java, id)
}

Related

Can I add companion extension without first having companion object within a class?

For the below code, I can add invoke extension to the Companion
operator fun MyValue.Companion.invoke(value: Int) =
MyValue(value.toString())
class MyValue(private val value: String) {
companion object
fun print() = println("value = $value")
}
This enable me to call something as below
MyValue(1).print()
But as you see originally MyValue don't need the companion object.
I wonder if MyValue is without the companion object, i.e.
class MyValue(private val value: String) {
fun print() = println("value = $value")
}
Is it possible for me to still create a Companion extension function? e.g.
operator fun MyValue.Companion.invoke(value: Int) =
MyValue(value.toString())
You can add a secondary constructor to your class that accept an Int,
class MyValue(private val value: String) {
constructor(value: Int) : this(value.toString())
fun print() = println("value = $value")
}
Now you can call both, MyValue("10").print() and MyValue(10).print()

Ktor reified type parametar

I created class with generic in kotlin and want to use receive with generic, but I have error when i want to call.recieve type from generic:
Can not use MType as reified type parameter. Use a class instead.
Code:
class APIRoute<EType : IntEntity, MType : Any> {
fun Route.apiRoute() {
post {
val m = call.receive<MType>()
call.respond(f(model))
}
}
}
How to fix it?
You need to provide the expected type to the receive() function. Due to type erasure in Java/Kotlin, the type of MType is unknown at runtime, so it can't be used with receive(). You need to capture the type as KType or KClass object when constructing APIRoute.
KClass is easier to use, however it works with raw classes only, it doesn't support parameterized types. Therefore, we can use it to create e.g. APIRoute<*, String>, but not APIRoute<*, List<String>>. KType supports any type, but is a little harder to handle.
Solution with KClass:
fun main() {
val route = APIRoute<IntEntity, String>(String::class)
}
class APIRoute<EType : IntEntity, MType : Any>(
private val mClass: KClass<MType>
) {
fun Route.apiRoute() {
post {
val m = call.receive(mClass)
call.respond(f(model))
}
}
}
Solution with KType:
fun main() {
val route = APIRoute.create<IntEntity, List<String>>()
}
class APIRoute<EType : IntEntity, MType : Any> #PublishedApi internal constructor(
private val mType: KType
) {
companion object {
#OptIn(ExperimentalStdlibApi::class)
inline fun <EType : IntEntity, reified MType : Any> create(): APIRoute<EType, MType> = APIRoute(typeOf<MType>())
}
fun Route.apiRoute() {
post {
val m = call.receive<MType>(mType)
call.respond(f(model))
}
}
}

Kotlin: generate a Factory by class

We're trying to do some generic processing in kotlin. Basically, for a given class, we want to get the related Builder object. i.a. for any object that extends a GenericObject, we want a Builder of that Object.
interface Builder<T : GenericObject>
object ConcreteBuilder: Builder<ConcreteObject>
We'd need a function that will return ConcreteBuilder from ConcreteObject
Our current implementation is a Map:
val map = mapOf<KClass<out GenericObject>, Builder<out GenericObject>>(
ConcreteObject::class to ConcreteBuilder
)
Then we can get it with:
inline fun <reified T : GenericObject> transform(...): T {
val builder = map[T::class] as Builder<T>
...
However this isn't very nice as:
we need an explicit cast to Builder<T>
the map has no notion of T, a key and a value could be related to different types.
Is there any better way to achieve it?
A wrapper for the map could be:
class BuilderMap {
private val map = mutableMapOf<KClass<out GenericObject>, Builder<out GenericObject>>()
fun <T: GenericObject> put(key: KClass<T>, value: Builder<T>) {
map[key] = value
}
operator fun <T: GenericObject> get(key: KClass<T>): Builder<T> {
return map[key] as Builder<T>
}
}
This hides the ugliness, while not completely removing it.
To use:
val builderMap = BuilderMap()
builderMap.put(ConcreteObject::class, ConcreteBuilder)
builderMap.put(BetonObject::class, BetonBuilder)
// builderMap.put(BetonObject::class, ConcreteBuilder) – will not compile
val builder = builderMap[T::class]

Kotlin reified issue with parameterized type

Why does the following not work?
It seems that the type Foo is not passed properly, or is this just a well known "issue"?
Foo<T> fails
data class Foo<T>(val a: T)
data class Bar(val b: String)
val objectMapper = ObjectMapper().registerKotlinModule()
val jsonString = "{\"a\": { \"b\": \"str\" }}"
fun main() {
val parseJson = parseJson<Bar>()
}
private inline fun <reified T> parseJson(): T {
val readValue: Foo<T> = objectMapper.readValue(jsonString)
return readValue.a
}
class java.util.LinkedHashMap cannot be cast to class ..Bar
(java.util.LinkedHashMap is in module java.base of loader 'bootstrap';
..Bar is in unnamed module of loader 'app')
The issue in this case is that you're not specifying the class that ObjectMapper should try and map your jsonString to. The matter is further complicated by the Foo type being parametric.
You need to build a JavaType reference to pass to objectMapper.readValue.
private inline fun <reified T> parseJson(): T {
val javaType = objectMapper.typeFactory.constructParametricType(Foo::class.java, T::class.java)
val readValue: Foo<T> = objectMapper.readValue(jsonString, javaType)
return readValue.a
}

Kotlin How to create dynamic Object

In javascript we can do something like this
function putritanjungsari(data){
console.log(data.name)
}
let data = {
name:"putri",
div:"m4th"
}
putritanjungsari(data)
In kotlin, i'am creating a function that accept an object as parameter then read it's properties later, how to do that in kotlin that targeting JVM?
If I understood your question correct, you are trying to have a variable that associates keys with some value or undefined(null in kt) if none are found. You are searching for a Map
If you don't know what types you want, you can make a map of type Any? So
Map<String, Any?>
Which is also nullable
Map<String, Any>
If you don't want nullables
Your code for example:
fun putritanjungsari(data: Map<String, Any?>){
print(data["name"])
}
val data: Map<String, Any?> =mapOf(
"name" to "putri",
"div" to "m4th"
)
putritanjungsari(data)
Note that you can't add new keys or edit any data here, the default map is immutable. There is MutableMap (which is implemented the same, only it has a method to put new data)
You can apply the property design pattern to solve your problem.
Here is its implementation in Kotlin:
interface DynamicProperty<T> {
fun cast(value: Any?): T
fun default(): T
companion object {
inline fun <reified T> fromDefaultSupplier(crossinline default: () -> T) =
object : DynamicProperty<T> {
override fun cast(value: Any?): T = value as T
override fun default(): T = default()
}
inline operator fun <reified T> invoke(default: T) = fromDefaultSupplier { default }
inline fun <reified T> required() = fromDefaultSupplier<T> {
throw IllegalStateException("DynamicProperty isn't initialized")
}
inline fun <reified T> nullable() = DynamicProperty<T?>(null)
}
}
operator fun <T> DynamicProperty<T>.invoke(value: T) = DynamicPropertyValue(this, value)
data class DynamicPropertyValue<T>(val property: DynamicProperty<T>, val value: T)
class DynamicObject(vararg properties: DynamicPropertyValue<*>) {
private val properties = HashMap<DynamicProperty<*>, Any?>().apply {
properties.forEach { put(it.property, it.value) }
}
operator fun <T> get(property: DynamicProperty<T>) =
if (properties.containsKey(property)) property.cast(properties[property])
else property.default()
operator fun <T> set(property: DynamicProperty<T>, value: T) = properties.put(property, value)
operator fun <T> DynamicProperty<T>.minus(value: T) = set(this, value)
}
fun dynamicObj(init: DynamicObject.() -> Unit) = DynamicObject().apply(init)
You can define your properties these ways:
val NAME = DynamicProperty.required<String>() // throws exceptions on usage before initialization
val DIV = DynamicProperty.nullable<String>() // has nullable type String?
val IS_ENABLED = DynamicProperty(true) // true by default
Now you can use them:
fun printObjName(obj: DynamicObject) {
println(obj[NAME])
}
val data = dynamicObj {
NAME - "putri"
DIV - "m4th"
}
printObjName(data)
// throws exception because name isn't initialized
printObjName(DynamicObject(DIV("m4th"), IS_ENABLED(false)))
Reasons to use DynamicObject instead of Map<String, Any?>:
Type-safety (NAME - 3 and NAME(true) will not compile)
No casting is required on properties usage
You can define what the program should do when a property isn't initialized
Kotlin is statically typed language, so it required a param type to be precisely defined or unambiguously inferred (Groovy, for instance, addresses the case by at least two ways). But for JS interoperability Kotlin offers dynamic type.
Meanwhile, in your particular case you can type data structure to kt's Map and do not argue with strict typing.
You have to use Any and after that, you have to cast your object, like this
private fun putritanjungsari(data : Any){
if(data is Mydata){
var data = data as? Mydata
data.name
}
}
Just for the sake of inspiration. In Kotlin, you can create ad hoc objects:
val adHoc = object {
var x = 1
var y = 2
}
println(adHoc.x + adHoc.y)