Last active
June 7, 2019 05:15
-
-
Save SystemFw/6664bd6fe4e23c37b81cbb64249f2c0e to your computer and use it in GitHub Desktop.
Shapeless: Convert between any two compatible case classes, selecting a subset of the fields
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
object conversions { | |
// it requires the cats, shapeless, and kittens libraries | |
import cats._, implicits._, data.Reader | |
import cats.sequence._ | |
import shapeless._, labelled._ | |
implicit class Convert[A](a: A) { | |
def convertTo[B](fields: Set[String])( | |
implicit ev: Conversion.Between[A, B]) = Conversion.to[B](a, fields) | |
} | |
trait Conversion[T] { | |
type Out | |
def apply(t: T): Out | |
} | |
object Conversion { | |
@annotation.implicitNotFound( | |
"Make sure that ${O} has the same field names as ${I}, and Options of the field types") | |
type Between[I, O] = Conversion[I] { type Out = Reader[Set[String], O] } | |
class Converter[B] { | |
def apply[A](a: A, fields: Set[String])( | |
implicit ev: Conversion.Between[A, B]): B = | |
ev(a).run(fields) | |
} | |
def to[B] = new Converter[B] | |
implicit def all[A, Repr <: HList, B, O <: HList]( | |
implicit genA: LabelledGeneric.Aux[A, Repr], | |
genB: LabelledGeneric.Aux[B, O], | |
ev: Traverser.Aux[Repr, selectFields.type, Reader[Set[String], O]]) | |
: Conversion.Between[A, B] = new Conversion[A] { | |
type Out = Reader[Set[String], B] | |
def apply(a: A): Out = | |
genA.to(a).traverse(selectFields).map(genB.from) | |
} | |
object selectFields extends Poly1 { | |
implicit def caseField[K <: Symbol, V](implicit key: Witness.Aux[K]) | |
: Case.Aux[FieldType[K, V], | |
Reader[Set[String], FieldType[K, Option[V]]]] = | |
at[FieldType[K, V]] { v => | |
Reader { (fields: Set[String]) => | |
field[K] { | |
if (fields contains key.value.name) Some(v) | |
else None | |
} | |
} | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
object Test { | |
case class User(name: String, age: Int) | |
case class UserDTO(name: Option[String], age: Option[Int]) | |
import conversions._ | |
def a = User("John", 24).convertTo[UserDTO](Set("name")) | |
// res0: Test.UserDTO = UserDTO(Some(John),None) | |
case class WrongFieldNames(surname: Option[String], age: Option[Int]) | |
def b = User("John", 24).convertTo[WrongFieldNames](Set("name")) | |
/* Compile Error | |
* [error] Make sure that Test.WrongFieldNames has the same field names as Test.User, and Options of the field types | |
* [error] def b = User("John", 24).convertTo[WrongFieldNames](Set("id")) // compile error | |
*/ | |
case class WrongTypes(name: Option[String], age: Int) | |
def c = User("John", 24).convertTo[WrongTypes](Set("name")) | |
/* Compile Error | |
* [error] Make sure that Test.WrongTypes has the same field names as Test.User, and Options of the field types | |
* [error] def c = User("John", 24).convertTo[WrongFieldNames](Set("id")) // compile error | |
*/ | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I am getting an error while trying a normal copy, any ideas ? I am using ammonite as follows