Scala:将地图转换为案例类
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/20684572/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Scala: convert map to case class
提问by Caballero
Let's say I have this example case class
假设我有这个示例案例类
case class Test(key1: Int, key2: String, key3: String)
And I have a map
我有一张地图
myMap = Map("k1" -> 1, "k2" -> "val2", "k3" -> "val3")
I need to convert this map to my case class in several places of the code, something like this:
我需要在代码的几个地方将此地图转换为我的案例类,如下所示:
myMap.asInstanceOf[Test]
What would be the easiest way of doing that? Can I somehow use implicit for this?
这样做的最简单方法是什么?我可以以某种方式使用隐式吗?
采纳答案by wheaties
Two ways of doing this elegantly. The first is to use an unapply, the second to use an implicit class (2.10+) with a type class to do the conversion for you.
优雅地执行此操作的两种方法。第一个是使用 an unapply,第二个是使用带有类型类的隐式类 (2.10+) 为您进行转换。
1) The unapply is the simplest and most straight forward way to write such a conversion. It does not do any "magic" and can readily be found if using an IDE. Do note, doing this sort of thing can clutter your companion object and cause your code to sprout dependencies in places you might not want:
1) unapply 是编写这种转换的最简单、最直接的方法。它没有任何“魔法”,如果使用 IDE,很容易找到。请注意,执行此类操作可能会使您的伴生对象变得混乱,并导致您的代码在您可能不想要的地方产生依赖关系:
object MyClass{
def unapply(values: Map[String,String]) = try{
Some(MyClass(values("key").toInteger, values("next").toFloat))
} catch{
case NonFatal(ex) => None
}
}
Which could be used like this:
可以这样使用:
val MyClass(myInstance) = myMap
be careful, as it would throw an exception if not matched completely.
小心,因为如果不完全匹配,它会抛出异常。
2) Doing an implicit class with a type class creates more boilerplate for you but also allows a lot of room to expand the same pattern to apply to other case classes:
2) 使用类型类创建隐式类会为您创建更多样板,但也允许有很大的空间来扩展相同的模式以应用于其他案例类:
implicit class Map2Class(values: Map[String,String]){
def convert[A](implicit mapper: MapConvert[A]) = mapper conv (values)
}
trait MapConvert[A]{
def conv(values: Map[String,String]): A
}
and as an example you'd do something like this:
作为一个例子,你会做这样的事情:
object MyObject{
implicit val new MapConvert[MyObject]{
def conv(values: Map[String, String]) = MyObject(values("key").toInt, values("foo").toFloat)
}
}
which could then be used just as you had described above:
然后可以像上面描述的那样使用它:
val myInstance = myMap.convert[MyObject]
throwing an exception if no conversion could be made. Using this pattern converting between a Map[String, String]to any object would require just another implicit (and that implicit to be in scope.)
如果无法进行转换,则抛出异常。使用这种在 aMap[String, String]到任何对象之间转换的模式只需要另一个隐式(并且隐式在范围内。)
回答by jkschneider
Here is an alternative non-boilerplate method that uses Scala reflection (Scala 2.10 and above) and doesn't require a separately compiled module:
这是使用 Scala 反射(Scala 2.10 及更高版本)并且不需要单独编译的模块的替代非样板方法:
import org.specs2.mutable.Specification
import scala.reflect._
import scala.reflect.runtime.universe._
case class Test(t: String, ot: Option[String])
package object ccFromMap {
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]
}
}
class CaseClassFromMapSpec extends Specification {
"case class" should {
"be constructable from a Map" in {
import ccFromMap._
fromMap[Test](Map("t" -> "test", "ot" -> "test2")) === Test("test", Some("test2"))
fromMap[Test](Map("t" -> "test")) === Test("test", None)
}
}
}
回答by jkschneider
Jonathan Chow implements a Scala macro (designed for Scala 2.11) that generalizes this behavior and eliminates the boilerplate.
Jonathan Chow 实现了一个 Scala 宏(专为 Scala 2.11 设计),它概括了这种行为并消除了样板。
http://blog.echo.sh/post/65955606729/exploring-scala-macros-map-to-case-class-conversion
http://blog.echo.sh/post/65955606729/exploring-scala-macros-map-to-case-class-conversion
import scala.reflect.macros.Context
trait Mappable[T] {
def toMap(t: T): Map[String, Any]
def fromMap(map: Map[String, Any]): T
}
object Mappable {
implicit def materializeMappable[T]: Mappable[T] = macro materializeMappableImpl[T]
def materializeMappableImpl[T: c.WeakTypeTag](c: Context): c.Expr[Mappable[T]] = {
import c.universe._
val tpe = weakTypeOf[T]
val companion = tpe.typeSymbol.companionSymbol
val fields = tpe.declarations.collectFirst {
case m: MethodSymbol if m.isPrimaryConstructor ? m
}.get.paramss.head
val (toMapParams, fromMapParams) = fields.map { field ?
val name = field.name
val decoded = name.decoded
val returnType = tpe.declaration(name).typeSignature
(q"$decoded → t.$name", q"map($decoded).asInstanceOf[$returnType]")
}.unzip
c.Expr[Mappable[T]] { q"""
new Mappable[$tpe] {
def toMap(t: $tpe): Map[String, Any] = Map(..$toMapParams)
def fromMap(map: Map[String, Any]): $tpe = $companion(..$fromMapParams)
}
""" }
}
}
回答by Vincent
This works well for me,if you use Hymanson for scala:
这对我很有效,如果你使用 Hymanson 来做 Scala:
def from[T](map: Map[String, Any])(implicit m: Manifest[T]): T = {
val mapper = new ObjectMapper() with ScalaObjectMapper
mapper.convertValue(map)
}
Reference from:Convert a Map<String, String> to a POJO
回答by cmbaxter
I don't love this code, but I suppose this is possible if you can get the map values into a tuple and then use the tupledconstructor for your case class. That would look something like this:
我不喜欢这段代码,但我想如果您可以将映射值放入一个元组中,然后tupled为您的案例类使用构造函数,那么这是可能的。那看起来像这样:
val myMap = Map("k1" -> 1, "k2" -> "val2", "k3" -> "val3")
val params = Some(myMap.map(_._2).toList).flatMap{
case List(a:Int,b:String,c:String) => Some((a,b,c))
case other => None
}
val myCaseClass = params.map(Test.tupled(_))
println(myCaseClass)
You have to be careful to make sure the list of values is exactly 3 elements and that they are the correct types. If not, you end up with a None instead. Like I said, not great, but it shows that it is possible.
您必须小心确保值列表正好是 3 个元素并且它们是正确的类型。如果没有,你最终会得到一个 None 。就像我说的,不是很好,但它表明这是可能的。
回答by Kai Han
commons.mapper.Mappers.mapToBean[CaseClassBean](map)
Details: https://github.com/hank-whu/common4s

