How to map from DTO to domain when have nullable field in API and don't need it on domain model

huangapple go评论54阅读模式
英文:

How to map from DTO to domain when have nullable field in API and don't need it on domain model

问题

DTO 模型:

@Serializable
data class PrivilegeWidgetDto(
    val widgetHeader: String,
    val widgetText: String,
    val bottomSheet: BottomSheetDto? = null,
)

@Serializable
data class BottomSheetDto(
    val bottomSheetText: String? = null,
    val bottomSheetHeader: String? = null,
)

领域模型:

@Serializable
data class PrivilegeWidget(
    val widgetHeader: String? = null,
    val widgetText: String? = null,
    val bottomSheet: BottomSheetData? = null,
)

@Serializable
data class BottomSheetData(
    // 想要使这些字段非可空
    val bottomSheetText: String? = null,
    val bottomSheetHeader: String? = null,
)

我使用扩展函数进行映射:

fun PrivilegeWidgetDto.mapToDomain(): PrivilegeWidget {
    return PrivilegeWidget(
        widgetHeader = this.widgetHeader,
        widgetText = this.widgetText,
        bottomSheet = BottomSheetData(
            bottomSheetHeader = this.bottomSheet?.bottomSheetHeader,
            bottomSheetText = this.bottomSheet?.bottomSheetText
        )
    )
}

问题是,尽管 PrivilegeWidget 中的 bottomSheet 可能为空,但它自己的字段不应该是可空的(如果 bottomSheet 不为空,它总是包含 bottomSheetHeaderbottomSheetText)。否则,我必须使用可空值,尽管我不需要它们。

有没有好的处理方法?如果我像这样做,是否可以解决问题:

bottomSheet = BottomSheetData_(
    bottomSheetHeader = this.bottomSheet?.bottomSheetHeader!!,
    bottomSheetText = this.bottomSheet?.bottomSheetText!!
)

或者:

bottomSheet = BottomSheetData_(
    bottomSheetHeader = this.bottomSheet?.bottomSheetHeader ?: "",
    bottomSheetText = this.bottomSheet?.bottomSheetText ?: ""
)

更新:

如果我在 DTO 和领域对象中将 bottomSheetHeaderbottomSheetText 定义为非可空,我在映射器中会收到编译器错误,并面临相同的问题:

只允许在类型为 BottomSheetDto? 的可空接收者上进行安全的 (?.) 或非空断言 (!!.) 调用

如果 bottomSheet 是可空的,映射器会将其字段视为可空,即使它们是非可空的。因此,我必须在映射器中解决与可空性相关的相同问题。

英文:

I get a complex model from server with Retrofit. I simplified it to make it more obvious.
Two fields are obligatory, but bottomSheet is optional. The problem is that I have to make all the fields of domain model BottomSheetData nullable even though I don't need them nullable.

DTO models:

@Serializable
data class PrivilegeWidgetDto(
    val widgetHeader: String,
    val widgetText: String,
    val bottomSheet: BottomSheetDto? = null,
)

@Serializable
data class BottomSheetDto(
    val bottomSheetText: String? = null,
    val bottomSheetHeader: String? = null,
)

Domain models:

@Serializable
data class PrivilegeWidget(
    val widgetHeader: String? = null,
    val widgetText: String? = null,
    val bottomSheet: BottomSheetData? = null,
)

@Serializable
data class BottomSheetData(
    //want to make these fields not nullable
    val bottomSheetText: String? = null, 
    val bottomSheetHeader: String? = null,
)

I use extension function to map:

fun PrivilegeWidgetDto.mapToDomain() : PrivilegeWidget {
    return PrivilegeWidget(
        widgetHeader = this.widgetHeader,
        widgetText = this.widgetText,
        bottomSheet = BottomSheetData(
            bottomSheetHeader = this.bottomSheet?.bottomSheetHeader,
            bottomSheetText = this.bottomSheet?.bottomSheetText
        )
    )
}

The problem is that though bottomSheet in PrivilegeWidget may be null, its own fields should not be nullable (if bottomSheet != null it always contains bottomSheetHeader and bottomSheetText). Otherwise I have to work with nullable values even though I don't need them.

Is these any good practice to handle it? What if I make sth like this - are these solution ok?

    bottomSheet = BottomSheetData_(
        bottomSheetHeader = this.bottomSheet?.bottomSheetHeader!!,
        bottomSheetText = this.bottomSheet?.bottomSheetText!!
    )



    bottomSheet = BottomSheetData_(
        bottomSheetHeader = this.bottomSheet?.bottomSheetHeader ?: "",
        bottomSheetText = this.bottomSheet?.bottomSheetText ?: ""
    )

UPD:

If I define bottomSheetHeader and bottomSheetText as non-nullable in both DTO and Domain objects I get compiler error in mapper and face the same problem:

> Only safe (?.) or non-null asserted (!!.) calls are allowed on a
> nullable receiver of type BottomSheetDto?

If bottomSheet is nullable mapper treats its fields as nullable too even if they are non-nullable. So I have to solve same problems with nullability in mapper.

答案1

得分: 1

使用BottomSheetData类中的辅助构造函数来强制其字段非空是实现这一目标的一种方法:

data class BottomSheetData(
    val bottomSheetText: String,
    val bottomSheetHeader: String
) {
    constructor(bottomSheetText: String?, bottomSheetHeader: String?) : this(
        bottomSheetText ?: "",
        bottomSheetHeader ?: ""
    )
}

现在,当你创建BottomSheetData的实例时,你可以使用辅助构造函数来处理bottomSheet可为空的情况:

fun PrivilegeWidgetDto.mapToDomain(): PrivilegeWidget {
    return PrivilegeWidget(
        widgetHeader = this.widgetHeader,
        widgetText = this.widgetText,
        bottomSheet = this.bottomSheet?.let {
            BottomSheetData(
                bottomSheetText = it.bottomSheetText,
                bottomSheetHeader = it.bottomSheetHeader
            )
        }
    )
}
英文:

One way to achieve this is by using a secondary constructor in the BottomSheetData class to enforce non-nullability of its fields:

data class BottomSheetData(
    val bottomSheetText: String,
    val bottomSheetHeader: String
) {
    constructor(bottomSheetText: String?, bottomSheetHeader: String?) : this(
        bottomSheetText ?: "",
        bottomSheetHeader ?: ""
    )

Now, when you create an instance of BottomSheetData, you can use the secondary constructor to handle the case where bottomSheet is nullable

fun PrivilegeWidgetDto.mapToDomain(): PrivilegeWidget {
    return PrivilegeWidget(
        widgetHeader = this.widgetHeader,
        widgetText = this.widgetText,
        bottomSheet = this.bottomSheet?.let {
            BottomSheetData(
                bottomSheetText = it.bottomSheetText,
                bottomSheetHeader = it.bottomSheetHeader
            )
        }
    )
}

答案2

得分: 1

像这样添加两个单独的扩展函数并将 bottomSheetHeaderbottomSheetText 更改为非空:

fun PrivilegeWidgetDto.mapToDomain(): PrivilegeWidget {
    return PrivilegeWidget(
        widgetHeader = this.widgetHeader,
        widgetText = this.widgetText,
        bottomSheet = bottomSheet?.mapToDomain()
    )
}

fun BottomSheetDto.mapToDomain(): BottomSheetData {
    return BottomSheetData(
        bottomSheetHeader = bottomSheetHeader ?: "",
        bottomSheetText = bottomSheetText ?: ""
    )
}

编译器报错 只允许在可空接收器类型 BottomSheetDto? 上进行安全 (?.) 或非空断言 (!!.) 调用,因为在你的扩展函数中 this.bottomSheet 已经是可空的,任何其他操作都需要进行空值检查。

英文:

add 2 separate extension functions like this and change bottomSheetHeader and bottomSheetText as non-nullable

fun PrivilegeWidgetDto.mapToDomain() : PrivilegeWidget {
    return PrivilegeWidget(
        widgetHeader = this.widgetHeader,
        widgetText = this.widgetText,
        bottomSheet = bottomSheet?.mapToDomain()
    )
}

fun BottomSheetDto.mapToDomain(): BottomSheetData {
   return BottomSheetData(
        bottomSheetHeader = bottomSheetHeader,
        bottomSheetText = bottomSheetText
    )
}

Compiler complains about Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type BottomSheetDto? because in your exention function this.bottomSheet is already nullable and any other operation will require null checks.

huangapple
  • 本文由 发表于 2023年5月24日 19:16:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/76322960.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定