문제
스칼라를 변환 할 수있는 좋은 방법이 있습니까? case class
예를 들어, 예를 들어
case class MyClass(param1: String, param2: String)
val x = MyClass("hello", "world")
어떤 종류의 매핑으로, 예를 들어
getCCParams(x) returns "param1" -> "hello", "param2" -> "world"
사전 정의 된 클래스가 아니라 모든 사례 클래스에서 작동합니다. 기본 제품 클래스를 심문하는 메소드를 작성하여 케이스 클래스 이름을 꺼낼 수 있음을 알았습니다.
def getCCName(caseobj: Product) = caseobj.productPrefix
getCCName(x) returns "MyClass"
그래서 나는 비슷한 솔루션을 찾고 있지만 사례 클래스 필드를 찾고 있습니다. 솔루션이 Java Reflection을 사용해야 할 수도 있다고 생각하지만, 사례 클래스의 기본 구현이 변경되면 미래의 Scala 릴리스에서 깨질 수있는 무언가를 쓰는 것을 싫어합니다.
현재 스칼라 서버에서 작업하고 있으며 케이스 클래스를 사용하여 프로토콜과 모든 메시지 및 예외를 정의하고 있습니다. 그러나 클라이언트 구현을 위해 메시징 계층을 보내려면 Java지도로 변환해야합니다. 내 현재 구현은 각 사례 클래스의 번역을 별도로 정의하지만 일반화 된 솔루션을 찾는 것이 좋을 것입니다.
해결책
이것은 작동해야합니다 :
def getCCParams(cc: AnyRef) =
(Map[String, Any]() /: cc.getClass.getDeclaredFields) {(a, f) =>
f.setAccessible(true)
a + (f.getName -> f.get(cc))
}
다른 팁
케이스 클래스가 확장되기 때문입니다 제품 하나는 단순히 사용할 수 있습니다 .productIterator
필드 값을 얻으려면 :
def getCCParams(cc: Product) = cc.getClass.getDeclaredFields.map( _.getName ) // all field names
.zip( cc.productIterator.to ).toMap // zipped with all values
또는 대안으로 :
def getCCParams(cc: Product) = {
val values = cc.productIterator
cc.getClass.getDeclaredFields.map( _.getName -> values.next ).toMap
}
제품의 장점 중 하나는 전화 할 필요가 없다는 것입니다. setAccessible
그 가치를 읽기 위해 현장에서. 다른 하나는 ProductIterator가 반사를 사용하지 않는다는 것입니다.
이 예제는 다른 클래스를 확장하지 않고 생성자 외부의 필드를 선언하지 않는 간단한 사례 클래스와 함께 작동합니다.
누구든지 재귀 버전을 찾는 경우 @andrejs의 솔루션을 수정합니다.
def getCCParams(cc: Product): Map[String, Any] = {
val values = cc.productIterator
cc.getClass.getDeclaredFields.map {
_.getName -> (values.next() match {
case p: Product if p.productArity > 0 => getCCParams(p)
case x => x
})
}.toMap
}
또한 중첩 된 케이스 클래스를 모든 레벨의 둥지에서 맵으로 확장합니다.
일반적인 기능으로 만드는 데 관심이없는 경우 간단한 변형입니다.
case class Person(name:String, age:Int)
def personToMap(person: Person): Map[String, Any] = {
val fieldNames = person.getClass.getDeclaredFields.map(_.getName)
val vals = Person.unapply(person).get.productIterator.toSeq
fieldNames.zip(vals).toMap
}
scala> println(personToMap(Person("Tom", 50)))
res02: scala.collection.immutable.Map[String,Any] = Map(name -> Tom, age -> 50)
모양이없는 것을 사용할 수 있습니다.
허락하다
case class X(a: Boolean, b: String,c:Int)
case class Y(a: String, b: String)
LabelledGeneric 표현을 정의하십시오
import shapeless._
import shapeless.ops.product._
import shapeless.syntax.std.product._
object X {
implicit val lgenX = LabelledGeneric[X]
}
object Y {
implicit val lgenY = LabelledGeneric[Y]
}
TOMAP 방법을 제공하기 위해 두 가지 전자를 정의하십시오
object ToMapImplicits {
implicit class ToMapOps[A <: Product](val a: A)
extends AnyVal {
def mkMapAny(implicit toMap: ToMap.Aux[A, Symbol, Any]): Map[String, Any] =
a.toMap[Symbol, Any]
.map { case (k: Symbol, v) => k.name -> v }
}
implicit class ToMapOps2[A <: Product](val a: A)
extends AnyVal {
def mkMapString(implicit toMap: ToMap.Aux[A, Symbol, Any]): Map[String, String] =
a.toMap[Symbol, Any]
.map { case (k: Symbol, v) => k.name -> v.toString }
}
}
그런 다음 이렇게 사용할 수 있습니다.
object Run extends App {
import ToMapImplicits._
val x: X = X(true, "bike",26)
val y: Y = Y("first", "second")
val anyMapX: Map[String, Any] = x.mkMapAny
val anyMapY: Map[String, Any] = y.mkMapAny
println("anyMapX = " + anyMapX)
println("anyMapY = " + anyMapY)
val stringMapX: Map[String, String] = x.mkMapString
val stringMapY: Map[String, String] = y.mkMapString
println("anyMapX = " + anyMapX)
println("anyMapY = " + anyMapY)
}
어떤 인쇄
anymapx = map (c-> 26, b-> 자전거, a -> true)
anymapy = map (b-> 두 번째, a-> 첫 번째)
StringMapx = map (c-> 26, b-> 자전거, a -> true)
StringMapy = map (b-> 두 번째, a-> 첫 번째)
중첩 케이스 클래스 (따라서 중첩지도) 확인 또 다른 대답
솔루션 ProductCompletion
통역사 패키지 :
import tools.nsc.interpreter.ProductCompletion
def getCCParams(cc: Product) = {
val pc = new ProductCompletion(cc)
pc.caseNames.zip(pc.caseFields).toMap
}
JSON4를 사용하는 경우 다음을 수행 할 수 있습니다.
import org.json4s.{Extraction, _}
case class MyClass(param1: String, param2: String)
val x = MyClass("hello", "world")
Extraction.decompose(x)(DefaultFormats).values.asInstanceOf[Map[String,String]]
시작 Scala 2.13
, case class
ES (구현으로 Product
)가 제공됩니다 ProductElementNames 필드 이름에 반복기를 반환하는 메소드.
필드 값으로 필드 이름을 지핑함으로써 ProductIterator 우리는 일반적으로 관련을 얻을 수 있습니다 Map
:
// case class MyClass(param1: String, param2: String)
// val x = MyClass("hello", "world")
(x.productElementNames zip x.productIterator).toMap
// Map[String,Any] = Map("param1" -> "hello", "param2" -> "world")
나는 Nice에 대해 모른다 ... 그러나 이것은 적어도이 매우 기본적인 예에서는 효과가있는 것 같습니다. 일이 필요하지만 시작하기에 충분할 수 있습니까? 기본적으로 사례 클래스 (또는 다른 클래스 :/)에서 모든 "알려진"방법을 필터링합니다.
object CaseMappingTest {
case class MyCase(a: String, b: Int)
def caseClassToMap(obj: AnyRef) = {
val c = obj.getClass
val predefined = List("$tag", "productArity", "productPrefix", "hashCode",
"toString")
val casemethods = c.getMethods.toList.filter{
n =>
(n.getParameterTypes.size == 0) &&
(n.getDeclaringClass == c) &&
(! predefined.exists(_ == n.getName))
}
val values = casemethods.map(_.invoke(obj, null))
casemethods.map(_.getName).zip(values).foldLeft(Map[String, Any]())(_+_)
}
def main(args: Array[String]) {
println(caseClassToMap(MyCase("foo", 1)))
// prints: Map(a -> foo, b -> 1)
}
}
commons.mapper.Mappers.Mappers.beanToMap(caseClassBean)