【问题标题】:No TypeTag available for case class Type没有可用于案例类 Type 的 TypeTag
【发布时间】:2019-06-30 12:15:20
【问题描述】:

我想生成一个方法,它将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, _] 转换回Object

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

  1. 从输入中提取Map[String, _]
  2. 从输入中提取Map[String, Types]
  3. 将字段name 的值从Name 转换为Map[String, _]
  4. 还原第 3 步以取回 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.typeSignature 返回的runtime.universe.Type 转换为TypeTag : ClassTag

【问题讨论】:

  • 这听起来像 Shapeless 可以解决 (但我对 Shapeless 的了解并不多,无法确定)。另外,Map[String, _] 看起来有点类似于 JSONHOCON,您是否尝试过搜索已经完成这项工作的库?喜欢 CirceSpary
  • @LuisMiguelMejíaSuárez 谢谢,我会看看你提到的图书馆。
  • Circe 实际上使用 shapeless 作为它的自动推导,所以你肯定是在正确的轨道上!我没有足够的时间浏览所有内容,所以我不完全确定您的意图是什么,但我建议您查看 Typeclasses 和可能的 Shapeless。您能否添加一个鼓舞人心的示例,您希望该功能的最终使用情况是否一切正常?
  • @Ethan 所以动机基本上是这样的。我得到一个对象,我对对象的结构一无所知(不知道属性的名称,它的类型,如果它们是嵌套的......)。但是他们会给我一条通往我需要修改的一个或多个属性的路径,以及到达该属性的路径。例如:更改为大写 Documents.name.firstName。所以我需要访问具有此属性作为变量的对象中的属性(这就是我转换为映射的原因),然后一旦我对目标应用操作,我应该得到一个对象。
  • 当你说你有一个对象时,你的意思是 java.lang.Object 还是其他类似 Json 对象的东西?

标签: scala reflection scala-reflect


【解决方案1】:

我不完全确定我是否正确解释了您的问题,但据我了解,这可以很好地解决并通过 shapeless 安全地输入。首先,您希望将您的Document 转换为Mapshapeless 可以使用 ops 文件夹中的 Typeclasses 为您开箱即用地执行此操作。如果我们将其捆绑到一个函数中,并通过一些机制将所有内容组合在一起,我们会得到如下结果:

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 类型类。但是,我们希望能够指定类型参数,然后让编译器仍然验证泛型表示是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))

【讨论】:

  • 感谢 Ethan 的回答。我读过关于 shapeless 的文章,但他们要求我在不使用 3rd 方库的情况下解决它,因此我们对每个步骤都有更多的控制权。这是我的最终解决方案github.com/ignacio-alorre/Scala/blob/master/Reflection/…。但是,请留下这个答案,因为它对其他可以使用 shapeless 的用户非常有用。
  • 这个决定似乎有点疯狂,但考虑到你所拥有的反射解决方案似乎很好。您可能想研究宏来替换反射,它可能(或可能不会)在实践中变得更清晰。或者你总是可以只使用无形源代码的相关部分。
【解决方案2】:

type nameT = nameType.type 不正确。 nameType.type 是此特定变量 nameType 的(单例)类型,并且您想要 name 字段的类型。这意外地起作用了,因为实际上您没有在fromMap2 中使用TruntimeMirror(classTag[T].runtimeClass.getClassLoader) 可以在那里替换为currentMirror)。

你想在fromMapToObject 中调用你原来的fromMap。你知道name 中的universe.Type,找到fromMapTypeTagClassTag 隐式参数就足够了。 但仅仅找到T 是不够的。问题是,由于您使用运行时反射,因此您在运行时知道universe.Type(和TypeTagClassTag)。但是为了调用fromMap,您需要在编译时知道T。所以一种方法是use 编译时反射,即宏。其他方法是避免 T 并像您一样使用值参数。

Case class to map in Scala

Scala: convert map to case class

【讨论】:

  • 非常感谢您的解释。我会根据你的回答修改我的最终解决方案
  • 嗨,如果我删除了 T: ClassTag,在我正在使用的函数末尾:constructorMirror(constructorArgs:_*).asInstanceOf[T]。我怎么能把它也换掉?
  • @IgnacioAlorre 让方法返回Any,在调用点进行转换。
  • 好的,再次感谢。但是我怎么能得到nameType的classTag,而不使用type nameT = nameType.type。据我了解,您的第二部分答案 nameType 是 Universe.Type,但如果没有类型 nameT = nameType.type,我找不到从该值获取 classTag 的方法
【解决方案3】:

我找到了解决方案。由于我无法获得classTagtypeTag,我将函数 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)

即使这行得通,我相信它也是非常反模式的。所以我会留下这个问题,以防有人指出改进它的方法。

【讨论】:

    猜你喜欢
    • 2013-06-04
    • 2017-09-05
    • 2022-10-08
    • 2015-12-02
    • 1970-01-01
    • 1970-01-01
    • 2013-11-26
    • 2013-07-01
    • 2019-05-11
    相关资源
    最近更新 更多