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 case class cannot handle optional substructures
Does anyone know of a nice library that does this in just a few LOCs?
We can use 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
Then, if we have any types that are not primitive, we just need to add these to our anyToJson
along with an encoder/decoder pair that can encode/decode this type as something primitive.
E.g. we can represent java.sql.Timestamp
with Long
, then
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)
and we need to add the line to anyToJson
case n: Timestamp => n.asJson