FromMap
использует shapeless.Typeable
для преобразования значений в ожидаемый тип. Поэтому самый простой способ заставить ваш код работать, это определить экземпляр Typeable
для преобразования из String
в Int
(и дополнительные Typeable
экземпляры для любого типа значения, который появляется в ваших классах case):
implicit val stringToInt: Typeable[Int] = new Typeable[Int] {
override def cast(t: Any): Option[Int] = t match {
case t: String => Try(t.toInt).toOption
case _ => Typeable.intTypeable.cast(t)
}
override def describe: String = "Int from String"
}
Однако это не преднамеренное использование Typeable
, которое предназначено для подтверждения того, что переменная с типом Any
уже является экземпляром ожидаемого типа без какого-либо преобразования. Другими словами, он предназначен для безопасной реализации типа asInstanceOf
, который также может обойти удаление типа.
Для правильности вы можете определить свой собственный класс типов ReadFromMap
, который использует ваш собственный класс типов Read
для преобразования из String
s в ожидаемые типы. Вот простая реализация класса типов Read
(при условии Scala 2.12):
import scala.util.Try
trait Read[T] {
def apply(string: String): Option[T]
}
object Read {
implicit val readString: Read[String] = Some(_)
implicit val readInt: Read[Int] = s => Try(s.toInt).toOption
// Add more implicits for other types in your case classes
}
И вы можете скопировать и адаптировать реализацию FromMap
для использования этого Read
класса типов:
import shapeless._
import shapeless.labelled._
trait ReadFromMap[R <: HList] extends Serializable {
def apply(map: Map[Symbol, String]): Option[R]
}
object ReadFromMap {
implicit def hnil: ReadFromMap[HNil] = _ => Some(HNil)
implicit def hlist[K <: Symbol, V, T <: HList](implicit
keyWitness: Witness.Aux[K],
readValue: Read[V],
readRest: ReadFromMap[T]
): ReadFromMap[FieldType[K, V] :: T] = map => for {
value <- map.get(keyWitness.value)
converted <- readValue(value)
rest <- readRest(map)
} yield field[K](converted) :: rest
}
Тогда просто используйте этот новый класс типов в вашем Parser
:
class Parser[A] {
def from[R <: HList]
(m: Map[Symbol, String])
(implicit
gen: LabelledGeneric.Aux[A, R],
fromMap: ReadFromMap[R]
): Option[A] = fromMap(m).map(gen.from)
}