I want to generate a method which will convert an Object
into a Map[String, _]
, and later back from Map[String, _]
to Object
.
I generate the initial object as follows:
case class Name (firstName : String, lastName : String)
case class Documents (idx: String, name: Name, code: String)
val mName1 = Name("Roger", "Rabbit")
val myDoc = Documents("12", mName1, "ABCD")
Then following method converts a given Map[String, _]
into an Object
:
def fromMap[T : TypeTag: ClassTag ](m: Map[String,_]) = {
val rm = runtimeMirror(classTag[T].runtimeClass.getClassLoader)
val classTest = typeOf[T].typeSymbol.asClass
val classMirror = rm.reflectClass(classTest)
val constructor = typeOf[T].decl(termNames.CONSTRUCTOR).asMethod
val constructorMirror = classMirror.reflectConstructor(constructor)
val constructorArgs = constructor.paramLists.flatten.map( (param: Symbol) => {
val paramName = param.name.toString
if(param.typeSignature <:< typeOf[Option[Any]])
m.get(paramName)
else
m.get(paramName).getOrElse(throw new IllegalArgumentException("Map is missing required parameter named " + paramName))
})
constructorMirror(constructorArgs:_*).asInstanceOf[T]
}
And inside the following method I convert the initial Object
into a Map[String, _]
, and back to Object
(by invoking the method above):
def fromMapToObject(input: Any) : Unit= {
println("input: "+input)
//Converting an Object into a Map
val r = currentMirror.reflect(input)
val docAsMapValues = r.symbol.typeSignature.members.toStream
.collect{case s : TermSymbol if !s.isMethod => r.reflectField(s)}
.map(r => r.symbol.name.toString.trim -> r.get)
.toMap
println("intermediate: "+docAsMapValues)
val obj = fromMap[Documents](docAsMapValues)
println("output: "+obj)
}
So if I call:
fromMapToObject(myDoc)
Input and output will match.
Problem , trying to go a step further, I want now to do the same with the field name
, which is of type Name
. But I want this step to be generic, in the sense that without knowing what is the type of the field name
, I could convert it into a Map[String, _]
, and from Map[String, _]
back to Object
.
So what I will do now in fromMapToObject is:
Map[String, _]
Map[String, Types]
name
from Name
into a Map[String, _]
Name
This is how I am trying to approach this new scenario:
def fromMapToObject[T: TypeTag: ClassTag](input: Any) : Unit = {
println("input: "+input)
//Converting an Object into a Map
val r = currentMirror.reflect(input)
val docAsMapValues = r.symbol.typeSignature.members.toStream
.collect{case s : TermSymbol if !s.isMethod => r.reflectField(s)}
.map(r => r.symbol.name.toString.trim -> r.get)
.toMap
val docAsMapTypes = r.symbol.typeSignature.members.toStream
.collect{case s : TermSymbol if !s.isMethod => r.reflectField(s)}
.map(r => r.symbol.name.toString.trim -> r.symbol.typeSignature)
.toMap
// Here I extract from the map the value and type of the attribute name
val nameType = docAsMapValues("name")
val nameValue = docAsMapValues("name")
// Converting Name into a map
val r2 = currentMirror.reflect(nameValue)
val nameAsMapValues = r2.symbol.typeSignature.members.toStream
.collect{case s : TermSymbol if !s.isMethod => r2.reflectField(s)}
.map(r2 => r2.symbol.name.toString.trim -> r2.get)
.toMap
type nameT = nameType.type
val obj = fromMap[nameT](nameAsMapValues)
}
But I am getting the following error when compiling in Intellij:
Error:(111, 29) No TypeTag available for nameT
val obj = fromMap[nameT](nameAsMapValues)
I would like to know how could I convert that runtime.universe.Type
which is returned from r.symbol.typeSignature
into a TypeTag : ClassTag
I'm not completely certain that I'm interpreting your question correctly, but from what I understand this can be solved pretty nicely and type safely via shapeless. To start with, you want to convert your Document
to a Map
. shapeless
can do this out of the box for you with one of the Typeclasses in the ops
folder. If we bundle that up into a function, with some machinery to pull everything together, we get something like:
import shapeless._
def ObjectToMap[A, Repr <: HList](obj: A)(
implicit
gen: LabelledGeneric.Aux[A,Repr], //Convert to generic representation
toMap: ops.record.ToMap[Repr] //Convert generic representation to Map[Symbol,_]
) = toMap(gen.to(obj))
which outputs
val m = ObjectToMap(myDoc)
println(m) //Map('code -> ABCD, 'name -> Name(Roger,Rabbit), 'idx -> 12)
Going the other direction is a little bit more complicated. There exists a ops.maps.FromMap
typeclass. However, we want to be able to specify the type parameter, and then let the compiler still verify that the generic representation is an HList
, to match up with FromMap
's signature. Since dependent types don't work with other variables defined in the same parameter list, and we only get one implicit parameter list, we need to resort to a little bit of trickery to curry the type parameters:
trait MapToObject[A]{
def from[Repr <: HList](m: Map[_,_])(
implicit
gen: LabelledGeneric.Aux[A,Repr],
fromMap: ops.maps.FromMap[Repr]
): Option[A] = fromMap(m).map(gen.from)
}
object MapToObject{
def apply[A](
implicit gen: LabelledGeneric[A]
): MapToObject[A] = new MapToObject[A]{}
}
When we run the output of the previous chunk through that we get:
val doc = MapToObject[Documents].from(m)
println(doc) //Some(Documents(12,Name(Roger,Rabbit),ABCD))
type nameT = nameType.type
is incorrect. nameType.type
is (singleton) type of this specific variable nameType
and you want type of name
field. This accidentally worked because actually you don't use T
in fromMap2
( runtimeMirror(classTag[T].runtimeClass.getClassLoader)
can be replaced with currentMirror
there).
You wanted to call your original fromMap
inside fromMapToObject
. You know universe.Type
of name
and it's enough to find TypeTag
and ClassTag
implicit parameters for fromMap
. But it's not enough to find T
. The thing is that since you use runtime reflection you know universe.Type
(and TypeTag
, ClassTag
) at runtime. But in order to call fromMap
you need to know T
at compile time. So one way is to use compile-time reflection ie macros. Other way is to avoid T
and use value parameters like you did.
I was able to find a solution. Since I wasn't able to get classTag
and typeTag
I modified the function toMap as follow:
def fromMap2[T : ClassTag ](m: Map[String,_], mSymbol: Symbol, mType :Type): Any = {
val rm = runtimeMirror(classTag[T].runtimeClass.getClassLoader)
val classTest = mSymbol.asClass
val classMirror = rm.reflectClass(classTest)
val constructor = mType.decl(termNames.CONSTRUCTOR).asMethod
val constructorMirror = classMirror.reflectConstructor(constructor)
val constructorArgs = constructor.paramLists.flatten.map( (param: Symbol) => {
val paramName = param.name.toString
if(param.typeSignature <:< typeOf[Option[Any]])
m.get(paramName)
else
m.get(paramName).getOrElse(throw new IllegalArgumentException("Map is missing required parameter named " + paramName))
})
constructorMirror(constructorArgs:_*).asInstanceOf[T]
}
So now I need to pass the Type and Symbol of T. I can get these two values like follow:
//Converting an Object into a Map
val r = currentMirror.reflect(input)
val mapValues = r.symbol.typeSignature.members.toStream
.collect{case s : TermSymbol if !s.isMethod => r.reflectField(s)}
.map(r => r.symbol.name.toString.trim -> r.get)
.toMap
val mapTypes = r.symbol.typeSignature.members.toStream
.collect{case s : TermSymbol if !s.isMethod => r.reflectField(s)}
.map(r => r.symbol.name.toString.trim -> r.symbol.typeSignature)
.toMap
val mapTypesSymbols = r.symbol.typeSignature.members.toStream
.collect{case s : TermSymbol if !s.isMethod => r.reflectField(s)}
.map(r => r.symbol.name.toString.trim -> r.symbol.typeSignature.typeSymbol)
.toMap
val nameType = mapTypes("name")
val nameTypeSymbols = mapTypesSymbols("name")
val nameValue = mapValues("name")
// Converting Name into a map
val r2 = currentMirror.reflect(nameValue)
val nameAsMapValues = r2.symbol.typeSignature.members.toStream
.collect{case s : TermSymbol if !s.isMethod => r2.reflectField(s)}
.map(r2 => r2.symbol.name.toString.trim -> r2.get)
.toMap
type nameT = nameType.type
val obj = fromMap2[nameT](nameAsMapValues, nameTypeSymbols, nameType)
Even if this works, I believe it is very anti pattern. So I will leave the question open in case someone could point out ways to improve it.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.