如何使用 Scala 中的任何库将通用的潜在嵌套映射 Map[String, Any] 转换为 case class?
How to convert generic potentially nested map Map[String, Any] to case class using any library in Scala?
我对反思不太满意,这个使用无形的答案在某些情况下有效(但似乎有很多边缘情况)Shapeless code to convert Map[String, Any] to case class cannot handle optional substructures
有谁知道一个不错的库可以在几个 LOC 中执行此操作?
我找到了一种使用 Spray 的相当简洁的方法 Json
首先我们定义一种从Map[String, Any]
到达JsObject
的方法
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)
}
然后我们可以只使用 convertTo
,前提是我们在范围
中有隐式 JsonFormat
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))
}
注意事项
Spray json 只有开箱即用的 json格式化最多 22 个字段!
无法处理任何自定义类型,例如java.sql.Timestamp
,因为这不是 JSON 类型。
使用杰克逊:
libraryDependencies += "com.fasterxml.jackson.core" % "jackson-databind" % "2.9.8"
libraryDependencies += "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.9.8"
case class Foo(a: List[Int], b: Option[Double])
case class Bar(c: Int, d: String, e: Foo)
val mapper = new ObjectMapper().registerModule(DefaultScalaModule)
println(mapper.convertValue(Map(
"c" -> 3,
"d" -> "foo",
"e" -> Map("a" -> List(1, 2))), classOf[Bar]))
输出:Bar(3,foo,Foo(List(1, 2),None))
我们可以使用 circe
import io.circe._
import io.circe.generic.auto._
import io.circe.parser._
import io.circe.syntax._
def mapToJson(map: Map[String, Any]): Json =
map.mapValues(anyToJson).asJson
def anyToJson(any: Any): Json = any match {
case n: Int => n.asJson
case n: Long => n.asJson
case n: Double => n.asJson
case s: String => s.asJson
case true => true.asJson
case false => false.asJson
case null | None => None.asJson
case list: List[_] => list.map(anyToJson).asJson
case list: Vector[_] => list.map(anyToJson).asJson
case Some(any) => anyToJson(any)
case map: Map[String, Any] => mapToJson(map)
}
def mapToCaseClass[T : Decoder](map: Map[String, Any]): T = mapToJson(map).as[T].right.get
然后,如果我们有任何非原始类型,我们只需要将它们添加到我们的 anyToJson
以及可以 encode/decode 这种类型的 encoder/decoder 对原始。
例如我们可以用 Long
表示 java.sql.Timestamp
,然后
import cats.syntax.either._
import io.circe.Decoder
import io.circe.Encoder
implicit val decodeTimestamp: Decoder[Timestamp] = Decoder.decodeLong.emap(long =>
Either.catchNonFatal(new Timestamp(long)).leftMap(_ => "Timestamp")
)
implicit val encodeTimestamp: Encoder[Timestamp] = Encoder.encodeLong.contramap[Timestamp](_.getTime)
我们需要将行添加到 anyToJson
case n: Timestamp => n.asJson
我对反思不太满意,这个使用无形的答案在某些情况下有效(但似乎有很多边缘情况)Shapeless code to convert Map[String, Any] to case class cannot handle optional substructures
有谁知道一个不错的库可以在几个 LOC 中执行此操作?
我找到了一种使用 Spray 的相当简洁的方法 Json
首先我们定义一种从Map[String, Any]
JsObject
的方法
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)
}
然后我们可以只使用 convertTo
,前提是我们在范围
JsonFormat
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))
}
注意事项
Spray json 只有开箱即用的 json格式化最多 22 个字段!
无法处理任何自定义类型,例如java.sql.Timestamp
,因为这不是 JSON 类型。
使用杰克逊:
libraryDependencies += "com.fasterxml.jackson.core" % "jackson-databind" % "2.9.8"
libraryDependencies += "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.9.8"
case class Foo(a: List[Int], b: Option[Double])
case class Bar(c: Int, d: String, e: Foo)
val mapper = new ObjectMapper().registerModule(DefaultScalaModule)
println(mapper.convertValue(Map(
"c" -> 3,
"d" -> "foo",
"e" -> Map("a" -> List(1, 2))), classOf[Bar]))
输出:Bar(3,foo,Foo(List(1, 2),None))
我们可以使用 circe
import io.circe._
import io.circe.generic.auto._
import io.circe.parser._
import io.circe.syntax._
def mapToJson(map: Map[String, Any]): Json =
map.mapValues(anyToJson).asJson
def anyToJson(any: Any): Json = any match {
case n: Int => n.asJson
case n: Long => n.asJson
case n: Double => n.asJson
case s: String => s.asJson
case true => true.asJson
case false => false.asJson
case null | None => None.asJson
case list: List[_] => list.map(anyToJson).asJson
case list: Vector[_] => list.map(anyToJson).asJson
case Some(any) => anyToJson(any)
case map: Map[String, Any] => mapToJson(map)
}
def mapToCaseClass[T : Decoder](map: Map[String, Any]): T = mapToJson(map).as[T].right.get
然后,如果我们有任何非原始类型,我们只需要将它们添加到我们的 anyToJson
以及可以 encode/decode 这种类型的 encoder/decoder 对原始。
例如我们可以用 Long
表示 java.sql.Timestamp
,然后
import cats.syntax.either._
import io.circe.Decoder
import io.circe.Encoder
implicit val decodeTimestamp: Decoder[Timestamp] = Decoder.decodeLong.emap(long =>
Either.catchNonFatal(new Timestamp(long)).leftMap(_ => "Timestamp")
)
implicit val encodeTimestamp: Encoder[Timestamp] = Encoder.encodeLong.contramap[Timestamp](_.getTime)
我们需要将行添加到 anyToJson
case n: Timestamp => n.asJson