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

pl.touk.nussknacker.engine.api.CirceUtil.scala Maven / Gradle / Ivy

package pl.touk.nussknacker.engine.api

import com.github.ghik.silencer.silent
import io.circe
import io.circe.generic.extras.Configuration
import io.circe._

import java.net.{URI, URL}
import java.nio.charset.StandardCharsets
import scala.annotation.tailrec
import scala.jdk.CollectionConverters._

object CirceUtil {

  implicit val configuration: Configuration = Configuration.default.withDefaults
    .withDiscriminator("type")

  def decodeJson[T: Decoder](json: String): Either[circe.Error, T] =
    io.circe.parser.parse(json).flatMap(Decoder[T].decodeJson)

  def decodeJson[T: Decoder](json: Array[Byte]): Either[circe.Error, T] =
    decodeJson(new String(json, StandardCharsets.UTF_8))

  def decodeJsonUnsafe[T: Decoder](json: String): T = unsafe(decodeJson(json))

  def decodeJsonUnsafe[T: Decoder](json: String, message: String): T = unsafe(decodeJson(json), message)

  def decodeJsonUnsafe[T: Decoder](json: Array[Byte]): T = unsafe(decodeJson(json))

  def decodeJsonUnsafe[T: Decoder](json: Array[Byte], message: String): T = unsafe(decodeJson(json), message)

  def decodeJsonUnsafe[T: Decoder](json: Json): T = unsafe(Decoder[T].decodeJson(json))

  def decodeJsonUnsafe[T: Decoder](json: Json, message: String): T = unsafe(Decoder[T].decodeJson(json), message)

  private def unsafe[T](result: Either[circe.Error, T], message: String = "") = result match {
    case Left(error) =>
      throw DecodingError(
        s"Failed to decode${if (message.isBlank) "" else s" - $message"}, error: ${error.getMessage}",
        error
      )
    case Right(data) => data
  }

  final case class DecodingError(message: String, ex: Throwable) extends IllegalArgumentException(message, ex)

  object codecs {

    implicit def jMapEncoder[K: KeyEncoder, V: Encoder]: Encoder[java.util.Map[K, V]] =
      Encoder[Map[K, V]].contramap(_.asScala.toMap)

    implicit lazy val uriDecoder: Decoder[URI] = Decoder.decodeString.map(URI.create)
    implicit lazy val uriEncoder: Encoder[URI] = Encoder.encodeString.contramap(_.toString)

    implicit val urlEncoder: Encoder[URL] = Encoder.encodeString.contramap(_.toExternalForm)
    implicit val urlDecoder: Decoder[URL] = Decoder.decodeString.map(new URL(_))
  }

  implicit class RichACursor(val cursor: ACursor) extends AnyVal {

    def downAt(p: Json => Boolean): ACursor = {
      @tailrec
      def go(c: ACursor): ACursor = c match {
        case success: HCursor => if (p(success.value)) success else go(success.right)
        case other            => other
      }

      go(cursor.downArray)
    }

  }

  implicit class HCursorExt(val cursor: HCursor) extends AnyVal {

    @silent("deprecated")
    def toMapExcluding(keys: String*): Decoder.Result[Map[String, String]] = {
      val keysSet = keys.toSet
      cursor
        .as[Map[String, String]]
        .map {
          _.filterKeys(k => !keysSet.contains(k)).toMap
        }
    }

  }

  // Be default circe print all empty values as a nulls which can be good for programs because it is more explicit
  // that some value is null, but for people it generates a lot of unnecessary noice
  val humanReadablePrinter: Printer = Printer.spaces2.copy(dropNullValues = true)

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy