Skip to content

Instantly share code, notes, and snippets.

@fancellu
Last active August 29, 2015 14:05
Embed
What would you like to do?
package playpen
object CaseMapApp extends App {
import caseMapper.CaseMapper._
case class Address(firstLine:String, postcode:String, country:String)
case class Person(name: String, age: Int, address:Address)
val here=Address("26 Duncoding","KT17 4LX","UK")
val dino = Person("Dino", 47,here)
println("dino="+dino)
val dinoMap=toMap(dino)
println(s"dinoMap=$dinoMap")
println
val addrMap=Map("firstLine"->"18 milo way","postcode"->"KT18 4AA","country"->"UK")
val miloAddr=fromMap[Address](addrMap)
println(s"miloAddr=$miloAddr")
val miloMap=Map("name" -> "Milo", "age" -> 4,"address"->miloAddr)
println(s"miloMap=$miloMap")
println("milo="+fromMap[Person](miloMap))
println
val dinoRoundtrip=fromMap[Person](dinoMap)
println("dino roundtrip="+dinoRoundtrip)
println(dino==dinoRoundtrip))
}
package playpen
object CaseMapApp2 extends App {
import com.felstar.caseMapper.CaseMapper._
case class Address(firstLine:String, postcode:String, country:String)
case class Person(name: String, age: Int, homeAddress:Address,workAddress:Address)
val here=Address("26 Duncoding","KT17 4LX","UK")
val work=Address("Canary Wharf","E17","UK")
val dino = Person("Dino", 47,here,work)
println(s"dino=$dino")
val dinoMap=toMap2(dino)
println(s"dinoMap=$dinoMap")
println
val dinoRoundtrip=fromMap2[Person](dinoMap)
println(s"Dino roundtrip=$dinoRoundtrip")
}
package playpen
object CaseMapApp3 extends App {
import com.felstar.caseMapper.CaseMapper._
case class Address(firstLine:String, postcode:String, country:String)
case class Person(name: String, age: Int, homeAddress:Address,workAddress:Address)
case class Marriage(person1:Person, person2:Person)
val here=Address("26 Duncoding","KT17 4LX","UK")
val work=Address("Canary Wharf","E17","UK")
val dino = Person("Dino", 47,here,work)
val jenny = Person("Jenny", 45,here,here)
val marriage=Marriage(dino,jenny)
println(s"marriage=$marriage")
val marriageMap=toMap2(marriage)
println(s"marriageMap=$marriageMap")
println
val marriageRoundtrip=fromMap2[Marriage](marriageMap)
println(s"Marriage roundtrip=$marriageRoundtrip")
println(marriage==marriageRoundtrip)
}
package com.felstar.caseMapper
import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context
// For Scala 2.11.x and the ever changing macro APIs!
object CaseMapper {
type StringAnyMap = scala.collection.Map[String, Any]
trait Mappable[T] {
def toMap(cas: T): StringAnyMap
def toMap2(cas: T): StringAnyMap
def fromMap(map: StringAnyMap): T
def fromMap2(map: StringAnyMap): T
}
implicit def toMappable[T]: Mappable[T] = macro CaseMapperMacros.toMappable[T]
def toMap[T: Mappable](cas: T) = implicitly[Mappable[T]].toMap(cas)
def toMap2[T: Mappable](cas: T) = implicitly[Mappable[T]].toMap2(cas) // this recurses, so case classes also expand to map, CaseMapApp2.scala
def fromMap[T: Mappable](map: StringAnyMap) = implicitly[Mappable[T]].fromMap(map)
def fromMap2[T: Mappable](map: StringAnyMap) = implicitly[Mappable[T]].fromMap2(map)
}
private class CaseMapperMacros(val c: Context) {
import c.universe._
def toMappable[T: c.WeakTypeTag] = {
val tpe = weakTypeOf[T]
tpe.normalize.typeSymbol.asClass.isCaseClass
val fields = tpe.decls.collectFirst {
case m: MethodSymbol if m.isPrimaryConstructor => m
}.get.paramLists.head
val (pairs, values) = fields.map { field =>
val name = field.name.toTermName
val decodedAsString = name.decodedName.toString
val retType = tpe.decl(name).typeSignature
// note on fromMap, we don't guard against class cast exception
// up to you if you want another policy
(q"$decodedAsString->cas.$name", q"map($decodedAsString).asInstanceOf[$retType]")
}.unzip
val (pairs2, values2) = fields.map { field =>
val name = field.name.toTermName
val decodedAsString = name.decodedName.toString
val retType = tpe.decl(name).typeSignature
val typeSymbol=retType.typeSymbol
if (typeSymbol.asClass.isCaseClass)
(q"$decodedAsString->com.felstar.caseMapper.CaseMapper.toMap2(cas.$name)",q"com.felstar.caseMapper.CaseMapper.fromMap2[$retType](map($decodedAsString).asInstanceOf[StringAnyMap])")
else (q"$decodedAsString->cas.$name",q"map($decodedAsString).asInstanceOf[$retType]")
}.unzip
val companionClass = tpe.typeSymbol.companion
q"""
new Mappable[$tpe] {
def toMap(cas: $tpe)=Map(..$pairs)
def toMap2(cas: $tpe)=Map(..$pairs2)
def fromMap(map: StringAnyMap): $tpe = $companionClass(..$values)
def fromMap2(map: StringAnyMap): $tpe = $companionClass(..$values2)
}
"""
}
}
dino=Person(Dino,47,Address(26 Duncoding,KT17 4LX,UK))
dinoMap=Map(name -> Dino, age -> 47, address -> Address(26 Duncoding,KT17 4LX,UK))
miloAddr=Address(18 milo way,KT18 4AA,UK)
miloMap=Map(name -> Milo, age -> 4, address -> Address(18 milo way,KT18 4AA,UK))
milo=Person(Milo,4,Address(18 milo way,KT18 4AA,UK))
dino roundtrip=Person(Dino,47,Address(26 Duncoding,KT17 4LX,UK))
true
dino=Person(Dino,47,Address(26 Duncoding,KT17 4LX,UK),Address(Canary Wharf,E17,UK))
dinoMap=Map(name -> Dino, age -> 47, homeAddress -> Map(firstLine -> 26 Duncoding, postcode -> KT17 4LX, country -> UK), workAddress -> Map(firstLine -> Canary Wharf, postcode -> E17, country -> UK))
Dino roundtrip=Person(Dino,47,Address(26 Duncoding,KT17 4LX,UK),Address(Canary Wharf,E17,UK))
marriage=Marriage(Person(Dino,47,Address(26 Duncoding,KT17 4LX,UK),Address(Canary Wharf,E17,UK)),Person(Jenny,45,Address(26 Duncoding,KT17 4LX,UK),Address(26 Duncoding,KT17 4LX,UK)))
marriageMap=Map(person1 -> Map(name -> Dino, age -> 47, homeAddress -> Map(firstLine -> 26 Duncoding, postcode -> KT17 4LX, country -> UK), workAddress -> Map(firstLine -> Canary Wharf, postcode -> E17, country -> UK)), person2 -> Map(name -> Jenny, age -> 45, homeAddress -> Map(firstLine -> 26 Duncoding, postcode -> KT17 4LX, country -> UK), workAddress -> Map(firstLine -> 26 Duncoding, postcode -> KT17 4LX, country -> UK)))
Marriage roundtrip=Marriage(Person(Dino,47,Address(26 Duncoding,KT17 4LX,UK),Address(Canary Wharf,E17,UK)),Person(Jenny,45,Address(26 Duncoding,KT17 4LX,UK),Address(26 Duncoding,KT17 4LX,UK)))
true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment