Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trim values of String fields of a case class

I am writing a generic function using shapeless that takes an instance of a case class and trims all values of fields that are string. The case class can have optional fields, nested objects, lists etc.

I have a case class Person.

case class Person(name: Option[String], address: List[String], friends: List[Person])

The function that I currently have:

import shapeless._, ops.hlist._

  object trimmer extends Poly1 {
    implicit val stringOptCase = at[Option[String]](_.map(_.trim))
    implicit val stringListCase = at[List[String]](_.map(_.trim))
    implicit def skipCase[A] = at[A](identity)
  }

  def trimStringValues[A, R <: HList](a: A)(implicit
                                     gen: Generic.Aux[A, R],
                                     mapper: Mapper.Aux[trimmer.type, R, R]
  ) = gen.from(mapper(gen.to(a)))

When I use the above function, it only works for the root level name field of class Person. It doesn't work for list or object field.

val person = Person(name = Some(" john "), address = List(" ny"," vegas "), friends = List(Person(Some(" alicia"), List(" peter"), Nil)))

trimStringValues(person) // Person(Some(john),List(ny, vegas),List(Person(Some( alicia),List( peter),List())))

How can I solve this ?

like image 497
oblivion Avatar asked Feb 08 '19 09:02

oblivion


1 Answers

First, it does seem to be working on address as well as name in your commented output, which is to be expected. It's not working on friends because List[Person] matches the skipCase case—it's neither an Option[String] or a List[String].

The easiest way to fix this is to use Shapeless's everywhere combinator. Given your code above, you can write the following:

scala> shapeless.everywhere(trimmer)(person)
res1: Person = Person(Some(john),List(ny, vegas),List(Person(Some(alicia),List(peter),List())))

In fact you can accomplish the same thing with an even simpler trimmer implementation:

object trimStrings extends Poly1 {
  implicit val stringCase: Case.Aux[String, String] = at[String](_.trim)
}

Or equivalently but even more concisely:

import shapeless.poly.->

object trimStrings extends (String -> String)(_.trim)

And then:

scala> shapeless.everywhere(trimStrings)(person)
res5: Person = Person(Some(john),List(ny, vegas),List(Person(Some(alicia),List(peter),List())))

If you wanted more control over exactly which strings are trimmed, you could return to your original implementation and either add an explicit List[Person] case, or a more generic case that would match types like this and apply trimmer recursively. Since you say you want to trim all strings, though, everywhere sounds like it's what you want.

like image 167
Travis Brown Avatar answered Sep 21 '22 14:09

Travis Brown