-
Notifications
You must be signed in to change notification settings - Fork 66
Description
Description
Presently, there are a few issues which make Kotlin Data Classes incompatible/difficult to use with Vert.x's @DataObject code generation (e.g. #17, #43).
For example, instead of using data classes idiomatically:
@DataObject
data class Author(val name: String, val email: String)you're forced to write code which is significantly lengthier and less 'safe': can be instantiated without all parameters, parameters are defined with mutable var instead of val, etc.:
@DataObject
class Author() {
lateinit var name: String
lateinit var email: String
constructor(json: JsonObject) : this() {
this.name = json.getString("name", "")
this.email = json.getString("email", "")
}
fun toJson(): JsonObject {
return JsonObject.mapFrom(this)
}
}From what I recall, the two main sources of ire are:
-
Required
JsonObjectconstructorData object classes must provide a constructor which takes a single io.vertx.core.json.JsonObject or java.lang.String parameter
Unlike Java, [secondary constructors in Kotlin must call the primary constructor](https ://kotlinlang.org/docs/classes.html#secondary-constructors) :
@DataObject data class Author(val name: String, val email: String) { // Illegal: name and email must be passed to this(). constructor(json: JsonObject): this() { AuthorConverter.fromJson(json, this) } // Legal, but difficult to maintain if your class has several parameters. constructor(json: JsonObject): this( name = json.getString("name"), email = json.getString("email") ) }
-
Required no-arg constructor (for the generated converter and
...Ofmethods)
As mentioned above, data classes must be instantiated with all the required parameters. That constraint makes them incompatible with the generated Converter.fromJson methods, as that requires the class to be instantiated, and the properties to be mutible:
public static void fromJson(Iterable<java.util.Map.Entry<String, Object>> json, YourType obj) {
for (java.util.Map.Entry<String, Object> member : json) {
switch (member.getKey()) {
case "aBoolean":
if (member.getValue() instanceof Boolean) {
obj.setABoolean((Boolean)member.getValue());
}
break;
...It also makes them incompatible with the generated yourTypeOf(...) methods, which similarly requires the class to be instantiated before it can populate values:
vertx-lang-kotlin/vertx-lang-kotlin/src/main/kotlin/io/vertx/kotlin/pgclient/PgConnectOptions.kt
Lines 148 to 152 in ae327c4
| user: String? = null): PgConnectOptions = io.vertx.pgclient.PgConnectOptions().apply { | |
| if (applicationLayerProtocols != null) { | |
| this.setApplicationLayerProtocols(applicationLayerProtocols.toList()) | |
| } |
Solutions
With everything I previously mentioned in mind, what can we do to improve the experience?
Some ideas off the top of my head:
-
@ConstructorBinding
Spring Boot introduced the@ConstructorBindingannotation, which eliminates the need for a no-arg constructor :@ConstructorBinding @ConfigurationProperties("blog") data class BlogProperties(var title: String, val banner: Banner) { data class Banner(val title: String? = null, val content: String) }
I'm not sure the feasibility of implementing something like this, as it's presumably dynamic and could lead to security issues. Neverthless, I'm including it here for inspiration.
-
Static
@JsonCreatormethod
In Jackson, you can define a static method annotated with@JsonCreator, which will be used to instantiate the class:data class UserId(private val value: String) { companion object { @JvmStatic @JsonCreator fun create(value: String) = UserId(value.toLowerCase()) } }
This could be an alternative to the
JsonObjectconstructor, and mitigate having to stuff values into thethis()block. -
Kotlinx.Serialization
Kotlinx.Serialization is a native serialization library which generates encodes and decoders at compile time.Perhaps we could leverage kotlinx.serialization to generate safe encoders and decoders?
-
Custom Codegen
We could augmentvertx-lang-kotlin-gen, or write a Kotlin Compiler Plugin, to generate Kotlin-friendly converters and...Ofmethods.