Нет TypeTag для класса дел - PullRequest
1 голос
/ 30 июня 2019

Я хочу сгенерировать метод, который преобразует 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. Извлечение из ввода a Map[String, _]
  2. Извлечение из ввода a 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)

Я хотел бы знать, как я могу преобразовать это runtime.universe.Type, который возвращается из r.symbol.typeSignature в TypeTag : ClassTag

Ответы [ 3 ]

1 голос
/ 03 июля 2019

Я не совсем уверен, что правильно интерпретирую ваш вопрос, но из того, что я понимаю, это можно решить довольно красиво и безопасно набрать с помощью бесформенного. Для начала вы хотите конвертировать Document в Map. shapeless может сделать это из коробки с одним из классов Typec в папке ops. Если мы объединим это в функцию, с помощью некоторого механизма, чтобы собрать все воедино, мы получим что-то вроде:

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))
1 голос
/ 04 июля 2019

type nameT = nameType.type неверно.nameType.type - это (одноэлементный) тип этой конкретной переменной nameType, и вы хотите указать тип поля name.Это случайно сработало, потому что на самом деле вы не используете T в fromMap2 (runtimeMirror(classTag[T].runtimeClass.getClassLoader) можно заменить на currentMirror там).

Вы хотели назвать свой оригинал fromMap внутри fromMapToObject.Вы знаете universe.Type из name и достаточно найти TypeTag и ClassTag неявных параметров для fromMap.Но этого недостаточно, чтобы найти T.Дело в том, что, поскольку вы используете отражение во время выполнения, вы знаете universe.TypeTypeTag, ClassTag) во время выполнения.Но чтобы вызвать fromMap, вам нужно знать T во время компиляции.Таким образом, один из способов - использовать отражение во время компиляции, т.е. макросы.Другой способ - избежать T и использовать параметры-значения, как вы это сделали.

Класс Case для отображения в Scala

Scala: преобразовать карту в класс Case

0 голосов
/ 30 июня 2019

Мне удалось найти решение.Поскольку я не смог получить classTag и typeTag, я изменил функцию на карту следующим образом:

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)

Даже если это работает, я считаю, что это очень анти паттерн.Поэтому я оставлю вопрос открытым, если кто-то может указать пути его улучшения.

...