Convert Nested Case Classes to Nested Maps in Scala

前端 未结 3 1239
野趣味
野趣味 2021-01-15 03:04

I have two nested case classes:

case class InnerClass(param1: String, param2: String)
case class OuterClass(myInt: Int, myInner: InnerClass)
val x = OuterCla         


        
相关标签:
3条回答
  • 2021-01-15 03:15

    As Luigi Plinge notes in a comment above, this is a very bad idea—you're throwing type safety out the window and will be stuck with a lot of ugly casts and runtime errors.

    That said, it's pretty easy to do what you want with the new 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])
    }
    

    And then:

    scala> println(anyToMap(x))
    Map(myInt -> 11, myInner -> Map(param1 -> hello, param2 -> world))
    

    Do not do this, though. In fact you should do your absolute best to avoid runtime reflection altogether in Scala—it's really almost never necessary. I'm only posting this answer because if you do decide that you must use runtime reflection, you're better off using the Scala Reflection API than Java's.

    0 讨论(0)
  • 2021-01-15 03:21

    Just call it recursively. So

    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)
      }
    
    0 讨论(0)
  • 2021-01-15 03:40

    Here is a more principled solution based on shapeless. 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]]
    }}}
    
    0 讨论(0)
提交回复
热议问题