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

msgpack4z.CirceMsgpack.scala Maven / Gradle / Ivy

The newest version!
package msgpack4z

import io.circe._
import scalaz.{-\/, \/, \/-, Equal}

object CirceMsgpack {
  implicit val circeJsonEqual: Equal[Json] = Equal.equalA[Json]
  implicit val circeJsonObjectEqual: Equal[JsonObject] = Equal.equalA[JsonObject]
  implicit val circeJsonNumberEqual: Equal[JsonNumber] = Equal.equalA[JsonNumber]

  def jsonCodec(options: CirceUnpackOptions): MsgpackCodec[Json] =
    new CodecCirceJson(options)

  def jsonObjectCodec(options: CirceUnpackOptions): MsgpackCodec[JsonObject] =
    new CodecCirceJsonObject(options)

  def allCodec(options: CirceUnpackOptions): (MsgpackCodec[Json], MsgpackCodec[JsonObject]) =
    (
      jsonCodec(options),
      jsonObjectCodec(options)
    )

  def jsonObject2msgpack(packer: MsgPacker, obj: JsonObject): Unit = {
    val fields = obj.toList
    packer.packMapHeader(fields.size)
    fields.foreach { field =>
      packer.packString(field._1)
      json2msgpack(packer, field._2)
    }
    packer.mapEnd()
  }

  def jsonArray2msgpack(packer: MsgPacker, array: Seq[Json]): Unit = {
    packer.packArrayHeader(array.size)
    array.foreach { x => json2msgpack(packer, x) }
    packer.arrayEnd()
  }

  def json2msgpack(packer: MsgPacker, json: Json): Unit = {
    json.fold(
      jsonNull = {
        packer.packNil()
      },
      jsonBoolean = value => {
        packer.packBoolean(value)
      },
      jsonNumber = value => {
        value.toLong match {
          case Some(l) =>
            packer.packLong(l)
          case None =>
            value.toBigDecimal match {
              case Some(b) =>
                if (b.isWhole && Long.MinValue <= b && b < BigDecimalLongMax2x) {
                  packer.packBigInteger(b.underlying.toBigIntegerExact)
                } else {
                  packer.packDouble(value.toDouble)
                }
              case None =>
                packer.packDouble(value.toDouble)
            }
        }
      },
      jsonString = string => {
        packer.packString(string)
      },
      jsonArray = array => {
        jsonArray2msgpack(packer, array)
      },
      jsonObject = obj => {
        jsonObject2msgpack(packer, obj)
      }
    )
  }

  def msgpack2json(unpacker: MsgUnpacker, unpackOptions: CirceUnpackOptions): UnpackResult[Json] = {
    val result = Result.empty[Json]
    if (msgpack2json0(unpacker, result, unpackOptions)) {
      \/-(result.value)
    } else {
      -\/(result.error)
    }
  }

  def msgpack2jsonObject(unpacker: MsgUnpacker, unpackOptions: CirceUnpackOptions): UnpackResult[JsonObject] = {
    val result = Result.empty[JsonObject]
    if (msgpack2jsObj0(unpacker, result, unpackOptions)) {
      \/-(result.value)
    } else {
      -\/(result.error)
    }
  }

  def msgpack2jsonArray(unpacker: MsgUnpacker, unpackOptions: CirceUnpackOptions): UnpackResult[List[Json]] = {
    val result = Result.empty[List[Json]]
    if (msgpack2jsArray0(unpacker, result, unpackOptions)) {
      \/-(result.value)
    } else {
      -\/(result.error)
    }
  }

  private[this] final case class Result[A](
    var value: A,
    var error: UnpackError
  )
  private[this] object Result {
    def fromEither[A](e: UnpackError \/ A, result: Result[A]): Boolean =
      e match {
        case \/-(r) =>
          result.value = r
          true
        case -\/(l) =>
          result.error = l
          false
      }

    def empty[A >: Null]: Result[A] = Result[A](null, null)
  }

  private[this] def msgpack2jsObj0(unpacker: MsgUnpacker, result: Result[JsonObject], unpackOptions: CirceUnpackOptions): Boolean = {
    val size = unpacker.unpackMapHeader()
    var obj = JsonObject.empty
    var i = 0
    val mapElem = Result.empty[Json]
    var success = true

    def process(key: String): Unit = {
      if (msgpack2json0(unpacker, mapElem, unpackOptions)) {
        obj = obj.add(key, mapElem.value)
        i += 1
      } else {
        result.error = mapElem.error
        success = false
      }
    }

    while (i < size && success) {
      val tpe = unpacker.nextType()
      if (tpe == MsgType.STRING) {
        process(unpacker.unpackString())
      } else {
        unpackOptions.nonStringKey(tpe, unpacker) match {
          case Some(key) =>
            process(key)
          case None =>
            success = false
            result.error = Other("not string key")
        }
      }
    }
    unpacker.mapEnd()
    if (success) {
      result.value = obj
    }
    success
  }

  private[this] def msgpack2jsArray0(unpacker: MsgUnpacker, result: Result[List[Json]], unpackOptions: CirceUnpackOptions): Boolean = {
    val size = unpacker.unpackArrayHeader()
    val array = new Array[Json](size)
    var i = 0
    val arrayElem = Result[Json](null, null)
    var success = true
    while (i < size && success) {
      if (msgpack2json0(unpacker, arrayElem, unpackOptions)) {
        array(i) = arrayElem.value
        i += 1
      } else {
        result.error = arrayElem.error
        success = false
      }
    }
    unpacker.arrayEnd()
    if (success) {
      result.value = array.toList
    }
    success
  }

  private[this] val BigIntegerLongMax = java.math.BigInteger.valueOf(Long.MaxValue)
  private[this] val BigDecimalLongMax2x: BigDecimal = BigDecimal(Long.MaxValue) * 2
  private[this] val BigIntegerLongMin = java.math.BigInteger.valueOf(Long.MinValue)

  private def isValidLong(value: java.math.BigInteger): Boolean =
    (BigIntegerLongMin.compareTo(value) <= 0) && (value.compareTo(BigIntegerLongMax) <= 0)

  private[this] def msgpack2json0(unpacker: MsgUnpacker, result: Result[Json], unpackOptions: CirceUnpackOptions): Boolean = {
    unpacker.nextType() match {
      case MsgType.NIL =>
        unpacker.unpackNil()
        result.value = Json.Null
        true
      case MsgType.BOOLEAN =>
        if (unpacker.unpackBoolean()) {
          result.value = Json.True
        } else {
          result.value = Json.False
        }
        true
      case MsgType.INTEGER =>
        val value = unpacker.unpackBigInteger()
        if (isValidLong(value)) {
          result.value = Json.fromLong(value.longValue())
        } else {
          result.value = Json.fromBigDecimal(BigDecimal(value))
        }
        true
      case MsgType.FLOAT =>
        val f = unpacker.unpackDouble()
        if (f.isPosInfinity) {
          Result.fromEither(unpackOptions.positiveInf, result)
        } else if (f.isNegInfinity) {
          Result.fromEither(unpackOptions.negativeInf, result)
        } else if (java.lang.Double.isNaN(f)) {
          Result.fromEither(unpackOptions.nan, result)
        } else {
          result.value = Json.fromDoubleOrNull(f)
        }
        true
      case MsgType.STRING =>
        result.value = Json.fromString(unpacker.unpackString())
        true
      case MsgType.ARRAY =>
        val result0 = Result.empty[List[Json]]
        val r = msgpack2jsArray0(unpacker, result0, unpackOptions)
        result.error = result0.error
        result.value = Json.fromValues(result0.value)
        r
      case MsgType.MAP =>
        val result0 = Result.empty[JsonObject]
        val r = msgpack2jsObj0(unpacker, result0, unpackOptions)
        result.error = result0.error
        result.value = Json.fromJsonObject(result0.value)
        r
      case MsgType.BINARY =>
        Result.fromEither(unpackOptions.binary(unpacker), result)
      case MsgType.EXTENSION =>
        Result.fromEither(unpackOptions.extension(unpacker), result)
    }
  }
}

private final class CodecCirceJson(unpackOptions: CirceUnpackOptions)
  extends MsgpackCodecConstant[Json](
    CirceMsgpack.json2msgpack,
    unpacker => CirceMsgpack.msgpack2json(unpacker, unpackOptions)
  )

private final class CodecCirceJsonObject(unpackOptions: CirceUnpackOptions)
  extends MsgpackCodecConstant[JsonObject](
    CirceMsgpack.jsonObject2msgpack,
    unpacker => CirceMsgpack.msgpack2jsonObject(unpacker, unpackOptions)
  )




© 2015 - 2025 Weber Informatics LLC | Privacy Policy