I am learning Kotlin (coming from JS/TS background) and wondering if it is possible to access properties of a data class dynamically. For e.g.
data class Obj (private val name: String){}
val obj = Obj(name="sel")
would it be possible to do now:
val getValueOfField = "name"
println(obj[getValueOfField])
When I tried, it failed but also noticed type inference is an issue. When I did:
Obj::class.declaredMemberProperties.contains(getValueOfField)
to check if the field exists, it still failed to compile due to data inference issue.
No dynamic access is available without using reflection as the return type of such an operation would be unknown and so type safety would be lost.
You can do destructuring though which is almost as flexible:
data class Name (
val firstName: String,
val secondName: String
){}
fun main() {
val (name, _) = Name("Nie", "Selam")
println("$name is learning Kotlin") // prints Nie is learning Kotlin
}
See Kotlin playground.
I'm running into a problem when configuring an Exec-Task using an Extension-Property.
Problem
The configuration of my Exec-Task relies on a String-Property that is defined in an extension. Unfortunately, the property is not set yet, when configuring the Exec-Task. This leads to an TaskCreationException:
Could not create task ':myTask'.
org.gradle.api.internal.tasks.DefaultTaskContainer$TaskCreationException: Could not create task ':myTask'.
...
Caused by: org.gradle.api.internal.provider.MissingValueException: Cannot query the value of extension 'myConfig' property 'command' because it has no value available.
at org.gradle.api.internal.provider.AbstractMinimalProvider.get(AbstractMinimalProvider.java:86)
Example
abstract class ConfigExtension() {
abstract val command: Property<String>
}
class MyGradlePlugin : Plugin<Project> {
override fun apply(project: Project) {
val myConfig = project.extensions.create(
"myConfig",
ConfigExtension::class.java
)
val myTask = project.tasks.register(
"myTask",
Exec::class.java
) {
it.commandLine(myConfig.command.get())
}
}
}
The problem seems to be the myConfig.command.get() which circumvents the lazy evaluation.
Test
#Test fun `plugin registers task`() {
// Create a test project and apply the plugin
val project = ProjectBuilder.builder().build()
project.plugins.apply("com.example.plugin")
// Verify the result
assertNotNull(project.tasks.findByName("myTask"))
}
Question
Is there a way to configure the commandLine-Value in a lazy manner like gradle tasks should be configured? [1] [2]
I'm having problems with the jvm compiler.
I'm trying to write a factory method for classes. The factory method has an init() block that helps to define behaviour for the new object. While this method compiles for JVM, I encounter a problem when running it:
java.lang.ExceptionInInitializerError
Caused by: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method type.ProblematicKt.nullable, parameter $this$nullable
Apparently, the object isn't yet defined when I attempt to run the problematicInit() block. How do I fix this?
Seems to be a JVM problem. It seems to work for Javascript compilations. My understanding was that getProblematic would be hoisted, but what's inside the scope would be deferred until it's run designed to be run later - after the factory method is completed.
interface ProblematicBuilderScope {
fun problematicInit(getX: () -> ProblematicInterface)
}
fun getProblematic() = X
class Problematic(...): ProblematicInterface
// Factory method with init() block
val X = Problematic.factory(...) {
problematicInit{ getProblematic() }
}
fun factory(init: ProblematicBuilderScope.() -> Unit): Problematic {
val newObject = Problematic(...)
val scope = ProblematicBuilderScope(newObject)
scope.init()
return newObject
}
here is a cleaner simpler way to achieve the same builder implementation
interface ProblematicInterface
class Problematic(): ProblematicInterface
fun buildProblematic(init: Problematic.() -> Unit): Problematic {
val newObject = Problematic()
init(newObject)
return newObject
}
val x = buildProblematic {
// this object type inside this clouse is Problematic
}
Apache beam seems to be refusing to recognise Kotlin's Iterable. Here is a sample code:
#ProcessElement
fun processElement(
#Element input: KV<String, Iterable<String>>, receiver: OutputReceiver<String>
) {
val output = input.key + "|" + input.value.toString()
println("output: $output")
receiver.output(output)
}
I get the following weird error:
java.lang.IllegalArgumentException:
...PrintString, #ProcessElement processElement(KV, OutputReceiver), #ProcessElement processElement(KV, OutputReceiver):
#Element argument must have type org.apache.beam.sdk.values.KV<java.lang.String, java.lang.Iterable<? extends java.lang.String>>
Sure enough, if I replace Iterable with java.lang.Iterable, the same code works just fine. What am I doing wrong?
Version of depedencies:
kotlin-jvm: 1.3.21
org.apache.beam: 2.11.0
Here is a gist with full codes and stack trace:
https://gist.github.com/marcoslin/e1e19afdbacac9757f6974592cfd8d7f#file-apache-beam-iterable-notworking-kt
Update:
After a bit of trial and error, I found out that while List<String> throws similar exception but MutableList<String> actually works:
class PrintString: DoFn<KV<String, MutableList<String>>, String>() {
#ProcessElement
fun processElement(
#Element input: KV<String, MutableList<String>>, receiver: OutputReceiver<String>
) {
val output = input.key + "|" + input.value.toString()
println("output: $output")
receiver.output(output)
}
}
So, this reminded me that Kotlin's Immutable collection are actually only interface and that underlying collection is still mutable. However, attempt to replace Iterable with MutableIterable continue to raise the error.
Update 2:
I deployed my Kotlin Dataflow job using the MutableList per above and job failed with:
java.lang.RuntimeException: org.apache.beam.sdk.util.UserCodeException: java.lang.ClassCastException:
org.apache.beam.runners.dataflow.worker.util.BatchGroupAlsoByWindowViaIteratorsFn$WindowReiterable cannot be cast to java.util.List
at org.apache.beam.runners.dataflow.worker.GroupAlsoByWindowsParDoFn$1.output(GroupAlsoByWindowsParDoFn.java:184)
at org.apache.beam.runners.dataflow.worker.GroupAlsoByWindowFnRunner$1.outputWindowedValue(GroupAlsoByWindowFnRunner.java:102)
I had to switch back to use java.lang.Iterable.
I ran into this problem as well, when using a ParDo following a GroupByKey. It turns out that a #JvmWildcard annotation is needed in the Iterable generic type when writing a transformation that accepts the result of a GroupByKey.
See the contrived example below that reads a file and groups by the first character of each line.
class BeamPipe {
class ConcatLines : DoFn<KV<String, Iterable<#JvmWildcard String>>, KV<String, String>>() {
#ProcessElement
fun processElement(#Element input: KV<String, Iterable<#JvmWildcard String>>, receiver: OutputReceiver<KV<String, String>>) {
receiver.output(KV.of(input.key, input.value.joinToString("\n")))
}
}
fun pipe(options: PipelineOptions) {
val file =
"testFile.txt"
val p = Pipeline.create(options)
p.apply(TextIO.read().from(file))
.apply("Key lines by first character",
WithKeys.of { line: String -> line[0].toString() }
.withKeyType(TypeDescriptors.strings()))
.apply("Group lines by first character", GroupByKey.create<String, String>())
.apply("Concatenate lines", ParDo.of(ConcatLines()))
.apply("Write to files", FileIO.writeDynamic<String, KV<String, String>>()
.by { it.key }
.withDestinationCoder(StringUtf8Coder.of())
.via(Contextful.fn(ProcessFunction { it.value }), TextIO.sink())
.to("whatever")
.withNaming { key -> FileIO.Write.defaultNaming(key, ".txt") }
)
p.run()
}
}
This looks like a bug in the Beam Kotlin SDK. The Reflection analysis for your #ProcessElement method does not work correctly. You can probably work around this by using ProcessContext ctx instead of using the #Element parameter.
I am not very familiar with kotlin but it seems that you need to import import java.lang.Iterable before using it in your code.
May I know how to fix the issue when we get the iterable from groupbykey.create(). i could not groupbykey as you did javalang iterable
For those are experiencing this issue and found their way here, my current workaround to keep writing the pipeline in kotlin is to create a Java static class with function(s) that creates, contains, and processes your Iterable(s). The result (in non-iterable format) can then be passed back to the kotlin.
I've got the following data class
data class MyResponse<T>(val header: String,
val body: T)
I want to write a generic Kotlin function that can deserialise json to various MyResponse<SimpleBody> or MyResponse<ComplexBody>
class JsonSerialiser {
val mapper = jacksonObjectMapper()
fun <T> fromJson(message: String, classz: Class<T>): T {
return mapper.readValue(message, classz)
}
}
val response: MyResponse<SimpleBody> = jsonSerialiser.fromJson("""{"header":"myheader", "body":{"simpleBody":{"name": "A"}}}""", MyResponse::class.java)
data class SimpleBody(val name: String)
It failed to compile with this error message
Kotlin: Type inference failed. Expected type mismatch: inferred type is MyResponse<*> but MyResponse<SimpleBody> was expected
Anyone knows how I can get this to work? I could use MyResponse<*> and then cast it, but I don't feel that's the right way.
Thanks & regards
Tin
You don't need this, just use com.fasterxml.jackson.module.kotlin.readValue:
val response: MyResponse<SimpleBody> = jacksonObjectMapper().readValue("""{"header":"myheader", "body":{"name": "A"}}""")
Please note that I changed the json a bit so that Jackson can parse it without further modifications