Emil Kantis
08/10/2022, 10:02 PMEmployee
for instance.. I want it to have non-null values for a lot of fields which actually are nullable in the underlying system. So I would like to have my client deserialize the API response into a User
and then use a toEmployee
method that returns a ValidatedNel<Problem, Employee>
.. More in thread.Employee
class looks like:
data class Employee(
val id: UInt,
val firstName: String,
val lastName: String,
val workEmail: String,
val supervisorId: UInt,
val ssn: String,
val teamId: UInt?,
val teamName: String?,
val personalEmail: String?,
val phone: String?,
val bankClearingNumber: String,
val bankAccountNumber: String,
val taxationTable: UInt,
val address: String,
val zipCode: String,
val city: String,
val employmentAgreements: List<EmploymentAgreement>,
val children: List<Child>,
@SerialName("salary")
val salaries: List<Salary>,
)
but it turns out everything but id is nullable.. so the User
class looks like:
@Serializable
data class User(
val id: UInt,
val firstName: String?,
val lastName: String?,
val workEmail: String?,
val supervisorId: UInt?,
val ssn: String?,
val teamId: UInt?,
val teamName: String?,
val personalEmail: String?,
val phone: String?,
val bankClearingNumber: String?,
val bankAccountNumber: String?,
val taxationTable: UInt?,
val address: String?,
val zipCode: String?,
val city: String?,
val employmentAgreements: List<EmploymentAgreement>,
val children: List<Child>,
@SerialName("salary")
val salaries: List<Salary>,
) {
fun toEmployee(): ValidatedNel<Exception, Employee> = TODO()
}
simon.vergauwen
08/11/2022, 6:34 AMZipping everything field by field feels very tedious?You can probably write some more generic for your use-case using
memberProperties
.
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.reflect.full/member-properties.html
Something that iterates over all properties, and validates them while collecting the Invalid
. If the resulting List<Invalid>
is empty, then you can construct the Valid
case.zip
boilerplate.
It's what I typically see as the "necessary evil" to be precise, and total in all my functions.stojan
08/11/2022, 6:47 AMEmil Kantis
08/11/2022, 6:54 AMsimon.vergauwen
08/11/2022, 7:20 PMvalidatedA.zip(
...,
validatedJ.zip(validatedK, validatedL, ::Triple)
) { a, b, c, d, e, f, g, h, i, (j, k, l) -> ... }
Emil Kantis
08/11/2022, 7:32 PMfun EmployeeDTO.validated(): ValidatedNel<String, Employee> {
val validatedFirstName = fromNullable(firstName) { "Missing first name" }.toValidatedNel()
val validatedLastName = fromNullable(lastName) { "Missing last name" }.toValidatedNel()
val validatedEmail = fromNullable(workEmail ?: personalEmail) { "Missing email" }.toValidatedNel()
val validSsn = fromNullable(ssn) { "Missing SSN" }.toValidatedNel()
val validBankClearingNumber = fromNullable(bankClearingNumber) { "Missing bank clearing number " }.toValidatedNel()
val validBankAccountNumber = fromNullable(bankAccountNumber) { "Missing bank account number " }.toValidatedNel()
val validTaxationTable = fromNullable(taxationTable) { "Missing taxation table" }.toValidatedNel()
val agreements = employmentAgreements.map { it.validated() }.sequence()
return id.validNel().zip(
validatedFirstName,
validatedLastName,
validatedEmail,
supervisorId.validNel(),
validSsn,
phone.validNel(),
validBankClearingNumber,
validBankAccountNumber,
validTaxationTable,
agreements,
children.validNel(),
salaries.validNel(),
::Employee,
)
}
simon.vergauwen
08/12/2022, 7:14 AMWas hoping to use the constructor ref, guess that wont be doableYou can easily do this if you define a
zip
method with the same arity as the constructor. Let's say a data class
with 12
properties.
Granted.. it's rather boilerplate'y but also why we choose to limit to 9 params.. Otherwise it becomes really bloated in Arrow Core.
fun <E, A, B, C, D, E, F, G, H, I, J, K, L, M> Validated<E, A>.zip(
validatedB: Validated<E, B>, validatedC: Validated<E, C>, ....
) = zip(
...,
validatedJ.zip(validatedK, validatedL, ::Triple)
) { a, b, c, d, e, f, g, h, i, (j, k, l) -> ... }