4
votes

Is it possible to use shapeless to convert 1 object to another while

  • doing some minor transformations like converting Option[T] to T (without manually defining mapping for each class)

  • Ignoring missing fields

import shapeless._ import shapeless.syntax._

case class Cat(color: Option[Int], isFat: Boolean, newField: String)
case class Kitten(color: Int, isFat: Boolean)

val kitten = Kitten(2, true)

val genCat = Generic[Cat]
val genKit = Generic[Kitten]

val cat: Cat = genCat.from(genKit.to(kitten))

This fails with the following error
(which expands to) shapeless.::[Int,shapeless.::[Boolean,shapeless.HNil]]

1

1 Answers

0
votes

Here is a solution using the same ideas as in my previous answer.

  • doing minor transformations (Option to default value)
  • ignore tail fields

Of course there are certain restrictions.

object HListsFlatten {

  import shapeless.{::, HList, HNil}

  sealed trait DefaultValue[V] {
    def value: V
  }

  implicit val defaultInt: DefaultValue[Int] = new DefaultValue[Int] {
    override def value = 0
  }

  sealed trait HConv[From <: HList, To <: HList] {
    def convert(list: From): To
  }

  implicit def buildHConvNil: HConv[HNil, HNil] = new HConv[HNil, HNil] {
    override def convert(list: HNil): HNil = HNil
  }

  implicit def buildHConvShorten[H <: AnyVal, T <: HList]
    : HConv[::[H, T], ::[H, HNil]] = new HConv[::[H, T], ::[H, HNil]] {
    override def convert(list: ::[H, T]): ::[H, HNil] = {
      list.head :: HNil
    }
  }

  implicit def buildHConvOption[H, T <: HList, T2 <: HList](
      implicit conv: HConv[T, T2],
      default: DefaultValue[H]): HConv[::[Option[H], T], ::[H, T2]] =
    new HConv[::[Option[H], T], ::[H, T2]] {
      override def convert(list: ::[Option[H], T]): ::[H, T2] = {
        list.head.getOrElse(default.value) :: conv.convert(list.tail)
      }
    }

  implicit def buildHConv[H <: AnyVal, T <: HList, T2 <: HList](
      implicit conv: HConv[T, T2]): HConv[::[H, T], ::[H, T2]] =
    new HConv[::[H, T], ::[H, T2]] {
      override def convert(list: ::[H, T]): ::[H, T2] = {
        list.head :: conv.convert(list.tail)
      }
    }

  implicit def buildHConvString[T <: HList, T2 <: HList](
      implicit conv: HConv[T, T2]): HConv[::[String, T], ::[String, T2]] =
    new HConv[::[String, T], ::[String, T2]] {
      override def convert(list: ::[String, T]): ::[String, T2] = {
        list.head :: conv.convert(list.tail)
      }
    }

  def flatten[A <: HList, B <: HList](list: A)(implicit conv: HConv[A, B]): B =
    conv.convert(list)

}

Example:

import shapeless.Generic

case class Cat(color: Option[Int], isFat: Boolean, newField: String)
case class Kitten(color: Int, isFat: Boolean)

val cat = Cat(color = Some(3), isFat = true, "SomeValue")

val genCat = Generic[Cat]
val genKit = Generic[Kitten]

import HListsFlatten._

scala> val kitten = genKit.from(flatten(genCat.to(cat)))
kitten: Kitten = Kitten(3,true)