我想生成一种将Object
转换为Map[String, _]
,然后再从Map[String, _]
转换为Object
的方法。
我生成初始对象如下:
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")
然后下面的方法将给定的Map[String, _]
转换为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]
}
在以下方法中,我将初始的Object
转换为Map[String, _]
,然后又返回到Object
(通过调用上述方法):
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)
}
所以,如果我打电话给我
fromMapToObject(myDoc)
输入和输出将匹配。
问题,尝试进一步,我现在想对类型为name
的字段Name
做同样的事情。但我希望此步骤是通用的,因为在不知道字段name
的类型的情况下,我可以将其转换为Map[String, _]
,然后从Map[String, _]
返回到{ {1}}。
所以我现在在fromMapToObject中要做的是:
Object
Map[String, _]
Map[String, Types]
的值从name
转换为Name
Map[String, _]
类型的Object 这就是我尝试处理这种新情况的方式:
Name
但是在Intellij中编译时出现以下错误:
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)
}
我想知道如何将Error:(111, 29) No TypeTag available for nameT
val obj = fromMap[nameT](nameAsMapValues)
返回的runtime.universe.Type
转换为r.symbol.typeSignature
答案 0 :(得分:1)
我不确定我是否正确解释了您的问题,但是据我了解,这可以很好地解决并且可以通过无形安全键入。首先,您需要将Document
转换为Map
。 shapeless
可以通过ops
文件夹中的其中一个Typeclass为您方便地进行此操作。如果我们将其捆绑成一个函数,并用某种机器将所有东西组合在一起,我们将得到类似的东西:
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))
输出
val m = ObjectToMap(myDoc)
println(m) //Map('code -> ABCD, 'name -> Name(Roger,Rabbit), 'idx -> 12)
往另一个方向走有点复杂。存在一个ops.maps.FromMap
类型类。但是,我们希望能够指定type参数,然后让编译器仍然验证通用表示形式为HList
,以与FromMap
的签名相匹配。由于从属类型不能与在同一参数列表中定义的其他变量一起使用,并且我们只能得到一个隐式参数列表,因此我们需要采取一些技巧来咖喱类型参数:
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]{}
}
当我们运行前一个块的输出时,我们得到:
val doc = MapToObject[Documents].from(m)
println(doc) //Some(Documents(12,Name(Roger,Rabbit),ABCD))
答案 1 :(得分:1)
type nameT = nameType.type
不正确。 nameType.type
是此特定变量nameType
的(单例)类型,您需要name
字段的类型。这是偶然的,因为实际上您没有在T
中使用fromMap2
(runtimeMirror(classTag[T].runtimeClass.getClassLoader)
可以在那里替换为currentMirror
)。
您想在fromMap
内部调用原始fromMapToObject
。您知道universe.Type
中的name
,足以为fromMap
找到TypeTag
和ClassTag
隐式参数。
但这还不足以找到T
。事实是,由于您使用运行时反射,因此在运行时知道universe.Type
(以及TypeTag
,ClassTag
)。但是要调用fromMap
,您需要在编译时知道T
。因此,一种方法是use编译时反射,即宏。另一种方法是避免使用T
并像以前一样使用值参数。
答案 2 :(得分:0)
我能够找到解决方案。由于我无法获得classTag
和typeTag
,因此我将函数toMap修改如下:
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]
}
所以现在我需要传递T的类型和符号。我可以得到如下两个值:
//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)
即使这行得通,我也认为这是非常反模式的。因此,我会公开讨论这个问题,以防有人指出改进它的方法。