#scala #circe
#scala #circe
Вопрос:
Можно ли написать пользовательский декодер для Map [String, Any] с помощью circe? я нашел это, но это только преобразование в Json:
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)
}
Комментарии:
1. Вы можете сделать это (вручную), но это почти наверняка неправильно. У вас нет гарантии, что ваш тип среды выполнения будет любым из этих, и для конкретного ADT вы всегда можете получить как декодер, так и кодировщик. Если у вас есть
Any
где-нибудь, вы, скорее всего, испортили свой дизайн, поскольку вместо этого вы могли бы иметь что-то конкретное и использовать конкретную реализацию для этого типа.2. Если только вы не вынуждены использовать чью-то ошибочную реализацию, и в этом случае исходная проблема все еще нуждается в решении.
Ответ №1:
Этот декодер Circe преобразует свой Json в Map[String, Any]
:
import io.circe.JavaDecoder._
implicit val objDecoder: Decoder[Any] = {
case x: HCursor if x.value.isObject =>
x.value.as[java.util.Map[String, Any]]
case x: HCursor if x.value.isString =>
x.value.as[String]
case x: HCursor if x.value.isBoolean =>
x.value.as[Boolean]
case x: HCursor if x.value.isArray =>
x.value.as[java.util.List[Any]]
case x: HCursor if x.value.isNumber =>
x.value.as[Double]
case x: HCursor if x.value.isNull =>
x.value.as[Unit]}
import io.circe.parser._
parse("""{"foo": 1, "bar": null, "baz": {"a" : "hi", "b" : true, "dogs" : [{ "name" : "Rover", "age" : 4}, { "name" : "Fido", "age" : 5 }] } }""")
.getOrElse(Json.Null)
.as[Map[String, _]]. // <-- this is the call to convert from Circe Json to the Java Map
Было бы лучше сопоставить регистр по значению курсора, но эти классы не являются общедоступными.
Обратите внимание, что я закодировал null
как Unit
, что не совсем правильно — Scala и null
не работают хорошо, и вам может потребоваться адаптировать вашу реализацию к вашему варианту использования. На самом деле я полностью исключаю этот случай из своей собственной реализации, потому что я декодирую Json, который был закодирован из экземпляров Scala.
Вам также необходимо создать пользовательский Decoder
io.circe
для Java Map
и List
. Я сделал следующее, в котором используется закрытый для пакета код Circe, что делает его кратким, но также уязвимым для изменений в Circe и заставляет вас иметь свой собственный io.circe
пакет.
package io.circe
import java.util.{List => JavaList, Map => JavaMap}
import scala.collection.mutable
import scala.collection.immutable.{List => ScalaList, Map => ScalaMap}
import scala.jdk.CollectionConverters.{MapHasAsJava, SeqHasAsJava}
object JavaDecoder {
implicit final def decodeJavaList[A](implicit decodeA: Decoder[A]): Decoder[JavaList[A]] = new SeqDecoder[A, JavaList](decodeA) {
final protected def createBuilder(): mutable.Builder[A, JavaList[A]] =
ScalaList.newBuilder[A].mapResult(_.asJava)
}
implicit final def decodeJavaMap[K, V](implicit
decodeK: KeyDecoder[K],
decodeV: Decoder[V]
): Decoder[JavaMap[K, V]] = new JavaMapDecoder[K, V, JavaMap](decodeK, decodeV) {
final protected def createBuilder(): mutable.Builder[(K, V), JavaMap[K, V]] =
ScalaMap.newBuilder[K, V].mapResult(_.asJava)
}
}
package io.circe
import scala.collection.{Map => ScalaMap}
import scala.collection.mutable
import scala.jdk.CollectionConverters.MapHasAsJava
abstract class JavaMapDecoder[K, V, M[K, V] <: java.util.Map[K, V]](
decodeK: KeyDecoder[K],
decodeV: Decoder[V]
) extends Decoder[M[K, V]] {
private val mapDecoder = new MapDecoder[K, V, ScalaMap](decodeK, decodeV) {
override protected def createBuilder(): mutable.Builder[(K, V), ScalaMap[K, V]] =
ScalaMap.newBuilder[K, V]
}
override def apply(c: io.circe.HCursor): io.circe.Decoder.Result[M[K,V]] =
mapDecoder.apply(c).map(_.asJava.asInstanceOf[M[K, V]])
}
Черт возьми, я уже подумываю о том, чтобы отправить это в качестве PR в Circe.
Ответ №2:
import io.circe.syntax.EncoderOps
import io.circe.{Encoder, Json}
case class Person(name: String, age: Int)
object Person {
implicit val decoder: io.circe.Decoder[Person] = io.circe.generic.semiauto.deriveDecoder
implicit val encoder: io.circe.Encoder[Person] = io.circe.generic.semiauto.deriveEncoder
}
case class Home(area: Int)
object Home {
implicit val decoder: io.circe.Decoder[Home] = io.circe.generic.semiauto.deriveDecoder
implicit val encoder: io.circe.Encoder[Home] = io.circe.generic.semiauto.deriveEncoder
}
def jsonPrinter[A](obj: A)(implicit encoder: Encoder[A]): Json =
obj.asJson
jsonPrinter(Person("Eminem", 30))
jsonPrinter(Home(300))
Это делается с помощью дженериков, я надеюсь, это поможет