当前位置: 首页 > 知识库问答 >
问题:

案例类没有可用的TypeTag类型

黄飞翮
2023-03-14

我想生成一个方法,将对象转换为映射字符串,然后再从映射字符串返回到对象。

我按如下方式生成初始对象:

  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")

然后,以下方法将给定的映射[字符串,\u]转换为对象:

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)

输入和输出将匹配。

问题,尝试更进一步,我现在想对类型为名称的字段执行相同的操作。但我希望这一步骤是通用的,因为在不知道字段名称的类型的情况下,我可以将其转换为映射字符串,然后从映射字符串返回到对象。

所以我现在在fromMapToObject中要做的是:

  1. 从输入中提取
  2. 从输入中提取映射[字符串,类型]
  3. 将字段名称的值从名称转换为映射字符串
  4. 还原第三步以获取类型为Name的对象

这就是我尝试处理这个新场景的方式:

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)

}

但在Intellij中编译时,我遇到以下错误:

Error:(111, 29) No TypeTag available for nameT
    val obj = fromMap[nameT](nameAsMapValues)

我想知道如何转换运行时。宇宙键入从r.symbol返回的。将签名键入类型标签:ClassTag

共有3个答案

汤念
2023-03-14

我能够找到解决方案。由于我无法获取class TagtypeTag,我将函数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)

即使这样做有效,我相信这是非常反模式的。因此,我将保留这个问题,以防有人可以指出改进它的方法。

元俊雅
2023-03-14

类型nameT=nameType。类型不正确<代码>名称类型。type是此特定变量的(单例)类型nameType,您需要的类型是name字段。这意外地起到了作用,因为实际上您没有在MAP2中使用runtimeMirror(classTag[t]。runtimeClass.getClassLoader)可以替换为currentMirror。

您想在fromMapToObject中调用原始的fromMap。你知道宇宙。键入ofof,就可以为fromMap找到类型标签和类标签的隐式参数。但仅仅找到T是不够的。问题是,既然使用了运行时反射,您就知道了宇宙。在运行时键入(和类型标签,类别标签)。但是为了从map调用,您需要在编译时知道T。因此,一种方法是使用编译时反射,即宏。另一种方法是避免T,并像您一样使用值参数。

要在Scala中映射的案例类

Scala:将map转换为case类

邹华池
2023-03-14

我不能完全肯定我是否正确地解释了你的问题,但据我所知,这可以很好地解决,并通过shapeless安全地键入。首先,您需要将文档转换为地图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)

往另一个方向走有点复杂。存在操作。地图。FromMaptypeclass。然而,我们希望能够指定类型参数,然后让编译器仍然验证泛型表示是一个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))
 类似资料: