mapstruct-kotlin
mapstruct-kotlin copied to clipboard
Allow Builders to leverage Kotlin defaults, or kotlin builder notation when required fields are missing
ex data class in kotlin -
import com.github.pozo.KotlinBuilder
@KotlinBuilder
data class ContactInfo(val phoneNumbers: List<PhoneNumber> = emptyList(),
val emails: List<EmailAddress> = emptyList(),
val addresses: List<PostalAddress> = emptyList()
)
The create function within the generated builder looks like:
public ContactInfo create() { return new ContactInfo(phoneNumbers, emails, addresses); }
The create here uses an all args constructor. However, if the object builders never set a value for one mandatory field (addresses field, for example) the create() call using an all args constructor will try to set addresses to null
, which will throw an NPE type error since this value can't be null in the target kotlin object.
I would like a way for the create() method to:
- leverage the default values in Kotlin that an object may have (usually non-nullable types have this), setting a field to the default value if a default exists
This would be amazing and very helpful to generate more accurate builders!!
Thank you for reporting this issue. I'll look into it.
@natashashams I tried to reproduce the issue what you described without any luck. It seems the issue description is not that clear to me.
Could you please create a very basic project where you can reproduce the described null pointer ? You can use the debug
module as a template https://github.com/Pozo/mapstruct-kotlin/tree/master/debug
@Pozo I might be able to help clarify as I'm looking for this feature as well.
If a data class specifies a default value for a field in the constructor, the default is not propagated to the builder and will always be invoked with null
if the builder never set the field.
Passing null
to a class constructor field is different than passing no value for that field in Kotlin.
For example
data class Foo(
val bar: String = "bar"
)
import java.lang.String;
public final class FooBuilder {
private String bar;
public FooBuilder setBar(String bar) {
this.bar = bar;
return this;
}
public Foo create() {
return new Foo(bar);
}
public static FooBuilder builder() {
return new FooBuilder();
}
}
If no value is passed to the builder, it can result in a NullPointerException because bar
is non-nullable or the field would unexpectedly be set to null
in the nullable case.
val foo = FooBuilder
.builder()
.create()
// Created with 'new Foo(null)'
I also came across this problem and tried to fix it. However it seems not that easy to find the fields default values. The only way I found was accessing the java source code as described on stackoverflow article 6373145. However in the java sources the default values are "hidden" in a special constructor which is generated by Kotlin. It would be nice to access the original Kotlin code, but this seems to be even more cumbersome, see here.
As a workaround, to at least generate usable mappers, you can just specify your defaults again in the mapper definition. So for the example above you should create a mapper like this:
@Mapper
abstract class FooMapper {
@Mappings(value = [
Mapping(target = "bar", source = "bar")
])
abstract fun map(bar: String = "bar"): Foo
}