Tipo de security con campos opcionales en una class de datos para JSON

Estoy construyendo una REST-API en Kotlin y estoy tratando de escribir mis classs de datos de tal manera que también pueda usarlas para acceder a la API. Planeo tener una "biblioteca de datos" común, que se compartirá entre el server y el cliente utilizando la API.

Esto funciona muy bien hasta ahora, pero ahora necesito modelar campos opcionales en las respuestas JSON. Por ejemplo: tengo un object de User (accesible a través del punto final /user/{id} manera REST típica). Ahora, no siempre necesita, por ejemplo, el text "acerca de mí" del usuario, por lo que no está incluido en la respuesta. Sin embargo, si especifica el campo "aboutme" ( /user/{id}?fields=aboutme ) se includeá en la respuesta.

Podría modelar la class de datos de la siguiente manera:

 data class User(id: UUID, name: String, aboutMe: String?) 

Pero ahora tengo que hacer una comprobación nula en el campo cada vez que accedo a él, incluso cuando obviamente no es nulo. Me gustaría crear una interfaz segura de types para la API para que cuando lo haga, por ejemplo, myCoolApi.getUser({id}, User::aboutMe) obtenga un object de User donde aboutMe no puede aboutMe . Podría lograr esto con generics de alguna manera, creo, pero eso sería muy detallado tan pronto como haya múltiples campos opcionales involucrados.

Estoy interesado en cualquier sugerencia.

Según request, agregaré un poco más de código para mostrar lo que busco.

 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 

Un par de opciones para probar:

Para valores opcionales, puede usar una propiedad que no admite nulos con un valor pnetworkingeterminado como en

 data class User (val id: Long, val name: String, val aboutMe: String = "") 

Otra alternativa es posible si puede renunciar a la class de datos y, en su lugar, usar una class regular que admita la inheritance de la siguiente manera.

 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) } } 

Creo que puedes lograr creando múltiples constructores para la class de datos como se menciona en la documentation de 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) } 

o puede usar @JvmOverloads para crear un constructor basado en arguments pasados. Puede encontrar más sobre @JvmOverloads

 data class Users @JvmOverloads constructor(val id: String, val name: String, val aboutMe: String? = null) 

Una posible forma de hacerlo sería usar classs selladas :

 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 } 

Otra forma sería usar múltiples 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 } 

Ambas classs e interfaces selladas le permiten realizar la verificación en time de compilation de que no está accediendo a una propiedad aboutMe no presente. Sin embargo, dependiendo de la extensión de su API, puede ser mejor con interfaces que permiten una composition más fácil. Ambos methods se pueden acceder fácilmente a través de la expresión when cuando obtiene un parámetro genérico / principal en una function.