Hey.. Is there any good pattern for this use case?...
# arrow
e
Hey.. Is there any good pattern for this use case? I have an API that returns an
Employee
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.
My
Employee
class looks like:
Copy code
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:
Copy code
@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()
}
Zipping everything field by field feels very tedious? 🙂
s
Zipping 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.
It's not something that exists in Arrow, and probably shouldn't but could work well for your use-case. If you don't need this pattern a lot, I would just opt for the bit of
zip
boilerplate. It's what I typically see as the "necessary evil" to be precise, and total in all my functions.
s
Another alternative is to not write a dto and write a converter instead https://github.com/uberto/kondor-json
e
I suppose I could do that. I'm already using kotlinx serialization everywhere though, but I guess the pattern can be used there as well
Thanks guys, I think I'll try out both approaches and see how it ends up looking 🙂
@simon.vergauwen wont I run into issues with zipping due to too many properties? IIRC those methods have a max of 14 params or something?
s
They indeed have a limit, but you can easily compose them further by using an intermediate tuple.
Copy code
validatedA.zip(
  ...,
  validatedJ.zip(validatedK, validatedL, ::Triple)
) { a, b, c, d, e, f, g, h, i, (j, k, l) -> ... }
e
Was hoping to use the constructor ref, guess that wont be doable 😞 Running into lots of duplication of the names. Aside from that, I think this is working quite nicely 🙂
Copy code
fun 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,
   )
}
Suppose I can wrap some of the data into some other data class though, since it's split from the response model of the API now
s
Was hoping to use the constructor ref, guess that wont be doable
You 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.
Copy code
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) -> ... }