我正在Kotlin中构建一个REST-API,我正在尝试编写我的数据类,以便我也可以使用它们来访问API。 我打算有一个共同的“数据库”,它将使用API在服务器和客户端之间共享。
到目前为止这很有效,但现在我需要在JSON响应中建模可选字段。例如:我有一个User
对象(可以通过/user/{id}
端点以典型的REST方式访问)。现在,您并不总是需要例如用户的“关于我”文本,因此默认情况下它不包含在响应中。但是,如果您指定"aboutme"
字段(/user/{id}?fields=aboutme
),则该字段将包含在回复中。
我可以按如下方式对数据类进行建模:
data class User(id: UUID, name: String, aboutMe: String?)
但是现在每次访问它时我都必须对字段进行空检查,即使它显然不是空的。我想为API创建一个类型安全的前端,以便在我这样做时,myCoolApi.getUser({id}, User::aboutMe)
我会得到一个User
对象,其中aboutMe
不可为空。我认为,我可以通过某种方式用泛型实现这一点,但只要涉及多个可选字段,就会变得非常冗长。
我对任何建议感兴趣。
根据要求,我会添加更多代码来展示我的目标。
class MyApi {
fun getUser(id: UUID, vararg fields: KProperty1<User, *>): User
}
// usage:
val myApi: MyApi = TODO()
val userId: UUID = TODO()
val aboutMe: String = myApi.getUser(userId).aboutMe // does not compile, aboutMe field not specified so aboutMe is nullable
val aboutMe2: String = myApi.getUser(userId, User::aboutMe).aboutMe // compiles, about me field was specified and thus cannot be null
答案 0 :(得分:0)
我认为您可以通过在Class
的文档中提及为数据类创建多个构造函数来实现//data class User(val id: String, val name: String, val aboutMe: String? = null)
data class User(val id: String, val name: String) {
constructor(id: String, name: String, aboutMe: String? = null) : this(id, name)
}
或者你可以使用@JvmOverloads根据传递的参数创建构造函数。您可以找到有关@JvmOverloads
的更多信息data class Users @JvmOverloads constructor(val id: String, val name: String, val aboutMe: String? = null)
答案 1 :(得分:0)
尝试多种选择:
对于可选值,您可以使用具有默认值的非可空属性,如
data class User (val id: Long, val name: String, val aboutMe: String = "")
如果您可以放弃数据类,而是使用支持继承的常规类,则可以使用另一种替代方法。
open class User (val id: Long, val name: String, val aboutMe: String)
class User_ (id: Long, name: String, aboutMe: String?)
: User(id, name, aboutMe ?: "")
class MyCoolApi {
fun getUser(id: Long): User {
// do you db lookup or something like that
// val name = ... from db
// val aboutMe = ... from db
return User_(id, name, aboutMe)
}
}
答案 2 :(得分:0)
执行此操作的一种可能方法是使用sealed classes:
sealed class ApiUser(val id: UUID, val name: String)
class ApiUserPlain(id: UUID, name: String) :
ApiUser(id, name)
class ApiUserAbout(id: UUID, name: String, val aboutMe: String) :
ApiUser(id, name)
fun getUser(userId: Long): ApiUserPlain {
return ApiUserPlain(UUID.randomUUID(), userId.toString())
}
fun getUser(userId: Long, about: String): ApiUserAbout {
return ApiUserAbout(UUID.randomUUID(), userId.toString(), about)
}
fun test() {
val userId = 2L
val aboutMe: String = getUser(userId).aboutMe // does not compile
val aboutMe2: String = getUser(userId, "about").aboutMe // compiles
}
另一种方法是使用multiple interfaces:
interface ApiUserPlain {
val id: UUID
val name: String
}
interface ApiUserAbout {
val aboutMe: String
}
class PlainUser(
override val id: UUID,
override val name: String
) : ApiUserPlain
class AboutUser(
override val id: UUID,
override val name: String,
override val aboutMe: String
) : ApiUserPlain, ApiUserAbout
fun getUser(userId: Long): PlainUser {
return PlainUser(UUID.randomUUID(), userId.toString())
}
fun getUser(userId: Long, about: String): AboutUser {
return AboutUser(UUID.randomUUID(), userId.toString(), about)
}
fun test() {
val userId = 2L
val aboutMe: String = getUser(userId).aboutMe // does not compile
val aboutMe2: String = getUser(userId, "about").aboutMe // compiles
}
密封的类和接口都允许您完成编译时验证,即您没有访问不存在的aboutMe
属性。但是,根据API的扩展,您可能会更好地使用允许更轻松组合的界面。当您在函数中获得泛型/父参数时,可以通过when expression轻松访问这两种方法。