How to convert generic potentially nested map Map[String, Any] to case class using any library in Scala?

后端 未结 3 2034
挽巷
挽巷 2021-01-17 09:27

I\'ve not had much joy with reflection, this answer using shapeless works for some cases (but seems to have many edge cases) Shapeless code to convert Map[String, Any] to ca

3条回答
  •  栀梦
    栀梦 (楼主)
    2021-01-17 09:39

    I've found a reasonably neat way to do it using Spray Json

    First we define a way to get to a JsObject from a Map[String, Any]

    def mapToJsObject(map: Map[String, Any]): JsObject =
      JsObject(fields = map.mapValues(anyToJsValue))
    
    def anyToJsValue(any: Any): JsValue = any match {
      case n: Int => JsNumber(n)
      case n: Long => JsNumber(n)
      case n: Double => JsNumber(n)
      case s: String => JsString(s)
      case true => JsTrue
      case false => JsFalse
      case null | None => JsNull
      case list: List[_] => JsArray(list.map(anyToJsValue).toVector)
      case Some(any) => anyToJsValue(any)
      case map: Map[String, Any] => mapToJsObject(map)
    }
    

    Then we can just use convertTo provided we have the implicit JsonFormat in scope

    case class Address(street: String, zip: Int)
    case class Person(name: String, address: Address)
    
    implicit val addressFormat = jsonFormat2(Address.apply)
    implicit val personFormat = jsonFormat2(Person.apply)
    
    "Convert Person example map to Person JsObject" in {
      JsonUtils.mapToJsObject(
        Map(
          "name" -> "Tom",
          "address" -> Map("street" -> "Jefferson st", "zip" -> 10000)
        )
      ).convertTo[Person] must_=== Person("Tom", Address("Jefferson st", 10000))
    }
    

     CAVEATs

    Spray json only has out of box jsonFormat up to 22 fields!

    Can not handle any custom types, e.g. java.sql.Timestamp, since this isn't a JSON type.

提交回复
热议问题