2
votes

Aucun TypeTag disponible pour la classe de cas Type

Je souhaite générer une méthode qui convertira un Object en une Map [String, _] , et plus tard à partir de Map [String, _] en Object .

Je génère l'objet initial comme suit:

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

Ensuite, la méthode suivante convertit une Map donnée [String, _] en un Object:

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)

}

Et à l'intérieur de la méthode suivante, je convertis le Object code> dans une Map [String, _] , et retour à Object (en invoquant la méthode ci-dessus):

 fromMapToObject(myDoc)

Donc, si j'appelle:

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)

  }

L'entrée et la sortie correspondront.

Problème , j'essaie d'aller plus loin , Je veux maintenant faire de même avec le champ name , qui est de type Name . Mais je souhaite que cette étape soit générique, en ce sens que sans savoir quel est le type du champ name , je pourrais le convertir en une Map [String, _] , et de Map [String, _] retour à Object.

Donc ce que je vais faire maintenant dans fromMapToObject est:

  1. Extraire de l'entrée une Map [String, _]
  2. Extraire de l'entrée une Map [String, Types]
  3. Convertit la valeur du champ name de Name en une Map [String, _]
  4. Revenez à la 3ème étape pour récupérer un objet de type Name

Voici comment j'essaie d'aborder ce nouveau scénario:

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]
  }

Mais j'obtiens l'erreur suivante lors de la compilation dans Intellij:

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

Je voudrais savoir comment pourrais-je convertir ce runtime.universe.Type qui est renvoyé de r.symbol.typeSignature en un TypeTag: ClassTag


10 commentaires

Cela ressemble à quelque chose de Shapeless pourrait résoudre (mais je ne sais vraiment pas trop sur Shapeless pour être sûr) . De plus, Map [String, _] ressemble peu à un JSON ou un HOCON , si vous aviez essayé de rechercher des bibliothèques qui font déjà le travail ? Vous aimez Circe ou Spary ?


@ LuisMiguelMejíaSuárez merci, je vais jeter un œil aux bibliothèques que vous mentionnez.


Circe utilise en fait informe pour sa dérivation automatique, vous êtes donc définitivement sur la bonne voie! Je n'ai pas le temps de tout passer suffisamment en revue, donc je ne suis pas tout à fait sûr de votre intention, mais je recommanderais de regarder les classes de types et les éventuelles Shapeless. Pouvez-vous ajouter un exemple motivant, comment vous voulez que votre utilisation finale de cette fonction ressemble si tout fonctionnait correctement?


@Ethan Donc, la motivation est essentiellement la suivante. J'obtiens un objet, je ne sais rien de la structure de l'objet (je ne connais pas le nom des attributs, c'est des types, s'ils sont imbriqués ...). Mais ils me donneront un chemin vers un ou plusieurs attributs que je dois modifier ET aussi le chemin pour atteindre cet attribut. Par exemple: Passez en majuscules Documents.name.firstName. J'ai donc besoin d'accéder aux attributs d'un objet ayant cet attribut comme variable (c'est pourquoi j'ai converti en carte), puis une fois que j'applique les opérations sur la cible, je devrais récupérer un objet.


Quand vous dites que vous avez un objet, voulez-vous dire un java.lang.Object ou quelque chose d'autre comme une sorte d'objet Json?


@Ethan c'est un json stocké dans hbase que je lis et stocke dans une instance d'une classe de cas


Donc, quand vous dites que vous avez un Object, voulez-vous dire que vous avez en fait une classe de cas, une pile de JSON ou un java.lang.Object?


Savez-vous que toutes vos données dans cet exemple seront un Document ?


Une Map [Symbol, _] est-elle une forme de Map acceptable pour vous? Si vous avez absolument besoin d'une Map [String, _] , c'est possible, mais cela nécessitera juste un peu de travail supplémentaire.


@Ethan En réponse à vos questions: 1- J'ai une classe de cas. 2- Non, c'est pourquoi j'essaie de passer aux génériques. Nous avons beaucoup de classes de cas différentes, et ces classes de cas peuvent varier à l'avenir, donc je ciblais quelque chose de générique, donc je voulais développer quelque chose qui nécessite le plus de coder en dur tout ce qui concerne les classes de cas. 3- J'utilisais Map [String, _] car la String sera le nom de l'attribut dans la classe case, et la valeur pourrait être de n'importe quel type: String, Float, Documents ...


3 Réponses :


1
votes

J'ai pu trouver une solution. Comme je n'ai pas pu obtenir classTag et typeTag , j'ai modifié la fonction toMap comme suit:

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

Alors maintenant, je dois passer le type et le symbole de T. Je peux obtenir ces deux valeurs comme suit:

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]

  }

Même si cela fonctionne, je pense que c'est très anti-pattern. Je vais donc laisser la question ouverte au cas où quelqu'un pourrait indiquer des moyens de l'améliorer.


0 commentaires

1
votes

Je ne suis pas tout à fait sûr d'interpréter correctement votre question, mais d'après ce que j'ai compris, cela peut être résolu assez bien et taper en toute sécurité via informe. Pour commencer, vous souhaitez convertir votre Document en Carte . shapeless peut le faire directement pour vous avec l'une des classes de types dans le dossier ops . Si nous regroupons cela dans une fonction, avec des machines pour tout rassembler, nous obtenons quelque chose comme:

val doc = MapToObject[Documents].from(m)
println(doc) //Some(Documents(12,Name(Roger,Rabbit),ABCD))

qui génère

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]{}
}

Aller dans l'autre sens est un peu plus compliqué. Il existe une classe de types ops.maps.FromMap . Cependant, nous voulons pouvoir spécifier le paramètre de type, puis laisser le compilateur vérifier toujours que la représentation générique est une HList , pour correspondre à la signature de FromMap . Puisque les types dépendants ne fonctionnent pas avec d'autres variables définies dans la même liste de paramètres, et que nous n'obtenons qu'une seule liste de paramètres implicites, nous devons recourir à un peu de ruse pour curry les paramètres de type:

val m = ObjectToMap(myDoc)
println(m) //Map('code -> ABCD, 'name -> Name(Roger,Rabbit), 'idx -> 12)

Lorsque nous exécutons la sortie du bloc précédent, nous obtenons:

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


2 commentaires

Et merci pour la réponse Ethan. J'ai lu sur informe, mais ils m'ont demandé de le résoudre sans utiliser de bibliothèques tierces afin que nous ayons plus de contrôle sur chacune des étapes. Voici ma solution finale github.com/ignacio-alorre/Scala / blob / master / Reflection /… . Cependant, veuillez laisser cette réponse car elle peut être vraiment utile pour d'autres utilisateurs qui peuvent utiliser informe.


Cette décision semble un peu folle, mais étant donné ce que vous avez, la solution de réflexion semble bien. Vous voudrez peut-être examiner des macros pour remplacer la réflexion, cela pourrait (ou non) s'avérer un peu plus propre dans la pratique. Ou vous pouvez toujours simplement utiliser les bits pertinents du code source informe.



1
votes

type nameT = nameType.type est incorrect. nameType.type est le type (singleton) de cette variable spécifique nameType et vous voulez le type de champ name . Cela a accidentellement fonctionné car en fait, vous n'utilisez pas T dans fromMap2 ( runtimeMirror (classTag [T] .runtimeClass.getClassLoader) peut être remplacé par currentMirror ici).

Vous vouliez appeler votre fromMap d'origine dans fromMapToObject . Vous connaissez universe.Type de nom et il suffit de trouver TypeTag et ClassTag paramètres implicites pour fromMap . Mais il ne suffit pas de trouver T . Le fait est que puisque vous utilisez la réflexion à l'exécution, vous connaissez universe.Type (et TypeTag , ClassTag ) au moment de l'exécution. Mais pour appeler fromMap , vous devez connaître T au moment de la compilation. Une façon est donc de utiliser la réflexion au moment de la compilation, c'est-à-dire des macros. Une autre façon est d'éviter T et d'utiliser des paramètres de valeur comme vous l'avez fait.

Classe de cas à mapper dans Scala

Scala: convertir la carte en classe de cas


4 commentaires

Merci beaucoup pour l'explication. Je modifierai ma solution finale en fonction de votre réponse


Salut, au cas où je supprime le T: ClassTag, à la fin de la fonction que j'utilise: constructorMirror (constructorArgs: _ *). AsInstanceOf [T]. Comment pourrais-je remplacer cela aussi?


@IgnacioAlorre Let méthode return Any , lancez-le sur le site d'appel.


Ok merci encore. Mais comment pourrais-je obtenir le classTag de nameType, sans utiliser le type nameT = nameType.type. Comme je le comprends pour votre deuxième partie de la réponse, nameType est l'universe.Type, mais je ne trouve pas de moyen d'obtenir le classTag à partir de cette valeur sans le type nameT = nameType.type