gstackoverflow
gstackoverflow

Reputation: 37106

Is there way to have polymorphic behaviour for convertors from DTO to model?

I have endpoint which accepts BaseDto:

@JsonTypeInfo(...)
@JsonSubTypes({
    @JsonSubTypes.Type(...),
    ...
})
@RequestMapping(
    method = RequestMethod.POST,
    value = "/path",
    produces = { "application/json" },
    consumes = { "application/json" }
)

 fun foo(@Valid @RequestBody dto:BaseDto ):ResponseEntity<?> {
   ...
}

Dto has inheritance hierarchy:

class Child1Dto : BaseDto { 
class Child2Dto : BaseDto { 
class Child3Dto : BaseDto { 
...

So in the controller I have Child1Dto or Child2Dto or Child3Dto

Also I have Model BaseModel

class Child1Model : BaseModel {
class Child2Model : BaseModel {
class Child3Model : BaseModel {

Now I have following code:

fun BaseDto.toModel(): BaseModel =
    when (this) {
        is Child1Dto -> Child1Model(/*no args*/)
        is Child2Dto  -> Child2Model(/*no args*/)
        is Child3Dto -> Child3Model(/*some args from dto*/)
        else -> throw IllegalStateException("Unknown dto type ${this::class}")
    }

I don't like this code because of

  1. Dto knows about model
  2. Switch doesn't look nice. In most cases I try to replace switch with polymorphism but I don't know how to do it here.

Is there better way to rewrite this code ?

Upvotes: 0

Views: 112

Answers (3)

Obren Starović
Obren Starović

Reputation: 32

You can do that. You will have to have a switch or map somewhere, but it can be hidden from the controller.

  1. You add a mapping layer that maps models to DTOs. There is an abstract mapper, and 3 concrete mapper classes. That way the DTOs are no longer aware of the models.
  2. Create a centralized mapper, which exposes a single map function for the controller to use, and chooses the appropriate concrete mapper.

Centralized mapper:

object DtoMapperRegistry {
    private val mappers: Map<Class<out BaseDto>, DtoToModelMapper<out BaseDto, out BaseModel>> = mapOf(
        Child1Dto::class.java to Child1DtoMapper(),
        Child2Dto::class.java to Child2DtoMapper(),
        Child3Dto::class.java to Child3DtoMapper()
    )

    @Suppress("UNCHECKED_CAST")
    fun <D : BaseDto, M : BaseModel> map(dto: D): M {
        val mapper = mappers[dto::class.java]
            ?: throw IllegalStateException("No mapper found for ${dto::class}")
        return (mapper as DtoToModelMapper<D, M>).map(dto)
    }
}

To use it you just need a single call:

val model = DtoMapperRegistry.map(dto)

Upvotes: 0

Peng
Peng

Reputation: 1342

add a new method toModel for decouple the DTO from the Model

abstract class BaseDto {
    abstract fun toModel(): BaseModel
}

and then implement the Method in Each Child Class

class Child1Dto : BaseDto() {
    override fun toModel(): BaseModel {
        return Child1Model()
    }
}

class Child2Dto : BaseDto() {
    override fun toModel(): BaseModel {
        return Child2Model()
    }
}

class Child3Dto : BaseDto() {
    override fun toModel(): BaseModel {
        return Child3Model(/* map necessary arguments here */)
    }
}

Upvotes: 0

asgarov1
asgarov1

Reputation: 4171

You can check out @JsonSubTypes that allow polymorphic deserialization

So for example you can define a parent:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "fooProperty", visible = true)
@JsonSubTypes({
        @JsonSubTypes.Type(value = ChildClassOne.class, name = "foo"),
        @JsonSubTypes.Type(value = ChildClassTwo.class, name = "bar"),
        @JsonSubTypes.Type(value = ChildClassThree.class, name = "baz")
}) public abstract class BaseDto { 
    public String fooProperty;
}

then based on the value of fooProperty, JSON will be deserialized into the coresponding child class

Upvotes: 0

Related Questions