mcarleio / konvert

This kotlin compiler plugin is using KSP API and generates kotlin code to map one class to another
https://mcarleio.github.io/konvert/
Apache License 2.0
86 stars 8 forks source link

Non-constructor `val` properties #29

Open shrralis opened 11 months ago

shrralis commented 11 months ago

I have a situation when my DTO has a val field declared in its constructor and my Entity has the same val field but it is not defined in its constructor. The problem is, that field is being omitted and not handled in any way.

data class DTO(
    val a: String,
    val b: String,
    val field: MutableSet<String>,
)

class Entity(
    val a: String,
    val b: String,
) {
    val field: MutableSet<String> = mutableSetOf()
}

@Konverter
@KComponent
interface EntityConverter {

    @Konvert(
        mappings = [
            Mapping(target = "field", expression = "doSomething()"),
        ],
    )
    fun convertToEntity(dto: DTO): Entity
}

The problem is that the result implementation of the converter doesn't handle that field at all, it fills only constructor properties:

@Component
public object EntityConverterImpl : EntityConverter {
    @GeneratedKonverter(priority = 5_000)
    override fun convertToEntity(dto: DTO): Entity = Entity(
        a = dto.a,
        b = dto.b,
    )
}

I believe the problem is that field is immutable (val) but what do we do in such cases? Is there any functionality like MapStruct's AfterMapping? Or is there a way to fill that field using .addAll()?

shrralis commented 11 months ago

I believe it would be possible to declare a default fun in the Konverter, like:

@Konverter
@KComponent
interface EntityConverter {

    @Konvert(
        mappings = [
            Mapping(target = "field", expression = "doSomething()"),
        ],
    )
    fun convertToEntity(dto: DTO): Entity

    fun convertFully(dto: DTO) = convertToEntity(dto).apply { field.addAll(dto.field) }
}

But it seems to be redundant since the whole interface has 2 available functions and it would confuse people. I'd prefer to have something like:

@Konverter
@KComponent
interface EntityConverter {

    @Konvert(
        mappings = [
            Mapping(target = "field", expression = "doSomething()"),
        ],
        afterKonvert = "it.fillCollections(dto)", // <-- where `it` is a result Entity and `dto` is the function's param
    )
    fun convertToEntity(dto: DTO): Entity

    fun Entity.fillCollections(dto: DTO): Unit {
        this.field.addAll(dto.field)
    }
}

Is this considerable?