All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sc8s.akka.circe.CirceJsonSerializer.scala Maven / Gradle / Ivy

package net.sc8s.akka.circe

import akka.actor.ExtendedActorSystem
import akka.event.Logging
import akka.serialization.SerializerWithStringManifest
import io.circe.Printer
import io.circe.parser._
import io.circe.syntax.EncoderOps

import java.nio.charset.StandardCharsets
import scala.util.{Success, Try}

private[circe] final class CirceJsonSerializer(
                                                system: ExtendedActorSystem,
                                                registry: CirceSerializerRegistry,
                                              ) extends SerializerWithStringManifest {
  private val log = Logging.getLogger(system, getClass)

  // dropNullValues can cause issues with Map[_,Option[_]] https://github.com/circe/circe/issues/1544
  private val printer = Printer.noSpaces //.copy(dropNullValues = true)

  private val charset = StandardCharsets.UTF_8

  private val serializers: Map[String, CirceSerializer[AnyRef]] =
    registry.serializers.map(serializer =>
      manifest(serializer.entityClass) -> serializer.asInstanceOf[CirceSerializer[AnyRef]]
    ).toMap

  private val circeSerializerClassloaders = serializers.map(_._2.entityClass.getClassLoader).toList.distinct.to(LazyList)

  private val manifestRenames = serializers.values.flatMap(_.manifestRenames).toMap

  override def toBinary(o: AnyRef): Array[Byte] = {
    val manifest = this.manifest(o)
    log.debug(s"serializing $manifest")

    val serializer = findCodecAndMigrater(manifest)

    printer.print(serializer.codec(o)).getBytes(charset)
  }

  override def fromBinary(bytes: Array[Byte], manifest: String): AnyRef = {
    log.debug(s"deserializing $manifest")

    val serializer = findCodecAndMigrater(manifest)

    val json = parse(new String(bytes, charset)) match {
      case Left(value) =>
        throw value.copy(message = s"${value.message} of $manifest")

      case Right(value) =>
        value
    }

    val maybeRenamedClass = json.mapObject(obj =>
      (for {
        clazz <- obj("class").flatMap(_.asString)
        renamedTo <- serializer.descriptorRenames.get(clazz)
      } yield obj.add("class", renamedTo.asJson)).getOrElse(obj)
    )

    maybeRenamedClass.as(serializer.codec) match {
      case Left(value) => throw value.withMessage(s"${value.message} of $manifest")
      case Right(value) => serializer.migrate.unapply(value).getOrElse(value)
    }
  }

  override def identifier = 38495562

  private def allInterfaces(clazz: Class[_]): Seq[String] = {
    val interfaces = clazz.getInterfaces.toSeq
    if (interfaces.isEmpty) Seq(manifest(clazz))
    else manifest(clazz) +: interfaces.flatMap(allInterfaces)
  }

  private def findCodecAndMigrater(manifest: String) = {
    lazy val clazzFromClassLoaders = circeSerializerClassloaders
      .map(classLoader => Try(Class.forName(manifest, true, classLoader)))
      .collectFirst {
        case Success(clazz) => clazz
      }
      .getOrElse(throw new RuntimeException(s"Missing class $manifest in classloaders [${circeSerializerClassloaders.map(_.getName).mkString(", ")}]"))

    val clazz = manifestRenames.getOrElse(manifest, clazzFromClassLoaders)

    val classManifests = allInterfaces(clazz)
    serializers.find {
      case (value, _) => classManifests.contains(value)
    } match {
      case Some((_, serializer)) => serializer
      case None =>
        throw new RuntimeException(
          s"Missing circe serializer for [${classManifests.mkString(", ")}], " +
            s"defined are [${serializers.keys.mkString(", ")}]"
        )
    }
  }

  override def manifest(o: AnyRef) = manifest(o.getClass)

  def manifest(clazz: Class[_]) = clazz.getName
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy