Преобразовать вложенные классы дел во вложенные карты в Scala
У меня есть два вложенных класса case:
case class InnerClass(param1: String, param2: String)
case class OuterClass(myInt: Int, myInner: InnerClass)
val x = OuterClass(11, InnerClass("hello", "world"))
Который я хочу преобразовать во вложенные Карты типа Map[String,Any], чтобы получить что-то вроде этого:
Map(myInt -> 11, myInner -> Map(param1 -> hello, param2 -> world))
Конечно, решение должно быть универсальным и работать для любого случая.
Примечание. В этом обсуждении был дан хороший ответ о том, как сопоставить класс отдельного случая с картой. Но я не мог приспособить его к вложенным классам. Вместо этого я получаю:
Map(myInt -> 11, myInner -> InnerClass(hello,world)
3 ответа
Как отмечает Луиджи Плинге в комментарии выше, это очень плохая идея - вы выбрасываете безопасность типов в окно и будете застревать с множеством уродливых приведений и ошибок времени выполнения.
Тем не менее, довольно легко делать то, что вы хотите, с новым Scala 2.10 Reflection API:
def anyToMap[A: scala.reflect.runtime.universe.TypeTag](a: A) = {
import scala.reflect.runtime.universe._
val mirror = runtimeMirror(a.getClass.getClassLoader)
def a2m(x: Any, t: Type): Any = {
val xm = mirror reflect x
val members = t.declarations.collect {
case acc: MethodSymbol if acc.isCaseAccessor =>
acc.name.decoded -> a2m((xm reflectMethod acc)(), acc.typeSignature)
}.toMap
if (members.isEmpty) x else members
}
a2m(a, typeOf[A])
}
А потом:
scala> println(anyToMap(x))
Map(myInt -> 11, myInner -> Map(param1 -> hello, param2 -> world))
Не делай этого, хотя. На самом деле вы должны сделать все возможное, чтобы вообще избежать отражения во время выполнения в Scala - в действительности это почти никогда не требуется. Я только публикую этот ответ, потому что если вы решите, что должны использовать отражение во время выполнения, вам лучше использовать API Scala Reflection, чем Java.
Просто назови это рекурсивно. Так
def getCCParams(cc: AnyRef) =
(Map[String, Any]() /: cc.getClass.getDeclaredFields) {(a, f) =>
f.setAccessible(true)
val value = f.get(cc) match {
// this covers tuples as well as case classes, so there may be a more specific way
case caseClassInstance: Product => getCCParams(caseClassInstance)
case x => x
}
a + (f.getName -> value)
}
Вот более принципиальное решение, основанное на бесформенности. https://github.com/yongjiaw/datacrafts
class NoSchemaTest extends FlatSpec with ShapelessProduct.Implicits {
"Marshalling and unmarshalling with Map" should "be successful" in {
val op = NoSchema.of[TestClass]
assert(
op.operator.marshal(
Map(
"v1" -> 10,
"v5" -> Map("_2" -> 12),
"v3" -> Iterable(Seq("v21" -> 3)),
"v6" -> TestClass3(v31 = 5)
)) == TestClass(
v1 = 10,
v5 = (null, 12),
v3 = Some(Seq(Some(
TestClass2(
v21 = 3,
v22 = null
)))),
v6 = Some(TestClass3(v31 = 5)),
v2 = None,
v4 = null
)
)
assert(
op.operator.unmarshal(
TestClass(
v1 = 1,
v2 = null
)
) == Map(
"v1" -> 1,
"v2" -> null,
// the rest are default values
"v6" -> null,
"v5" -> Map("_2" -> 2, "_1" -> "a"),
"v4" -> null,
"v3" -> Seq(
Map(
"v21" -> 3,
"v22" -> Map("v" -> Map(), "v32" -> Seq(12.0), "v31" -> 0)
)
)
)
)
}
}
object NoSchemaTest {
case class TestClass(v1: Int,
v2: Option[Seq[Option[Double]]] = None,
v3: Option[Seq[Option[TestClass2]]] = Some(Seq(Some(TestClass2()))),
v4: Seq[Int] = null,
v5: (String, Int) = ("a", 2),
v6: Option[TestClass3] = None
)
case class TestClass2(v21: Int = 3,
v22: TestClass3 = TestClass3(0)
)
case class TestClass3(v31: Int,
v32: Iterable[Double] = Seq(12),
v: Map[String, Int] = Map.empty
)
}
trait DefaultRule extends Operation.Rule {
override def getOperator[V](operation: Operation[V]): Operation.Operator[V] = {
operation.context.noSchema match {
case _: Primitive[V] => new PrimitiveOperator[V](operation)
case shapeless: ShapelessProduct[V, _] =>
new ShapelessProductMapper[V](operation, shapeless)
case option: OptionContainer[_] =>
new OptionOperator[option.Elem](
option.element, operation.asInstanceOf[Operation[Option[option.Elem]]])
.asInstanceOf[Operation.Operator[V]]
case map: MapContainer[_] =>
new MapOperator[map.Elem](
map.element, operation.asInstanceOf[Operation[Map[String, map.Elem]]])
.asInstanceOf[Operation.Operator[V]]
case seq: SeqContainer[_] =>
new SeqOperator[seq.Elem](
seq.element, operation.asInstanceOf[Operation[Seq[seq.Elem]]])
.asInstanceOf[Operation.Operator[V]]
case iterable: IterableContainer[_] =>
new IterableOperator[iterable.Elem](
iterable.element, operation.asInstanceOf[Operation[Iterable[iterable.Elem]]])
.asInstanceOf[Operation.Operator[V]]
}}}