io.circe.JsonNumber.scala Maven / Gradle / Ivy
package io.circe
import cats.kernel.Eq
import io.circe.numbers.BiggerDecimal
import java.io.Serializable
import java.lang.StringBuilder
import java.math.{ BigDecimal => JavaBigDecimal }
/**
* A JSON number with optimization by cases.
*/
sealed abstract class JsonNumber extends Serializable {
private[circe] def toBiggerDecimal: BiggerDecimal
/**
* Return this number as a [[scala.math.BigDecimal]].
*/
def toBigDecimal: Option[BigDecimal]
/**
* Return this number as a [[scala.math.BigInt]] if it's a sufficiently small whole number.
*/
def toBigInt: Option[BigInt]
/**
* Convert this number to its best [[scala.Double]] approximation.
*
* Anything over `Double.MaxValue` will be rounded to `Double.PositiveInfinity` and anything below
* `Double.MinValue` is rounded to `Double.NegativeInfinity`.
*/
def toDouble: Double
/**
* Convert this number to its best [[scala.Float]] approximation.
*
* Anything over `Float.MaxValue` will be rounded to `Float.PositiveInfinity` and anything below
* `Float.MinValue` is rounded to `Float.NegativeInfinity`.
*/
def toFloat: Float
/**
* Return this number as a [[scala.Byte]] if it's a valid [[scala.Byte]].
*/
final def toByte: Option[Byte] = toLong match {
case Some(n) =>
val asByte: Byte = n.toByte
if (n == asByte) Some(asByte) else None
case None => None
}
/**
* Return this number as a [[scala.Short]] if it's a valid [[scala.Short]].
*/
final def toShort: Option[Short] = toLong match {
case Some(n) =>
val asShort: Short = n.toShort
if (n == asShort) Some(asShort) else None
case None => None
}
/**
* Return this number as an [[scala.Int]] if it's a valid [[scala.Int]].
*/
final def toInt: Option[Int] = toLong match {
case Some(n) =>
val asInt: Int = n.toInt
if (n == asInt) Some(asInt) else None
case None => None
}
/**
* Return this number as a [[scala.Long]] if it's a valid [[scala.Long]].
*/
def toLong: Option[Long]
/**
* Universal equality derived from our type-safe equality.
*/
override final def equals(that: Any): Boolean = that match {
case that: JsonNumber => JsonNumber.eqJsonNumber.eqv(this, that)
case _ => false
}
/**
* Hashing that is consistent with our universal equality.
*/
override final def hashCode: Int = toBiggerDecimal.hashCode
private[circe] def appendToStringBuilder(builder: StringBuilder): Unit
}
private sealed abstract class BiggerDecimalJsonNumber(input: String) extends JsonNumber {
final def toBigInt: Option[BigInt] = toBiggerDecimal.toBigInteger.map(BigInt(_))
final def toBigDecimal: Option[BigDecimal] =
toBiggerDecimal.toBigDecimal.map(
value =>
if (value == JavaBigDecimal.ZERO) BigDecimal(JavaBigDecimal.ZERO)
else {
try BigDecimal(input)
catch {
case _: NumberFormatException => value
}
}
)
final def toLong: Option[Long] = toBiggerDecimal.toLong
override final def toString: String = input
private[circe] final def appendToStringBuilder(builder: StringBuilder): Unit = builder.append(input)
}
/**
* Represent a valid JSON number as a `String`.
*/
private[circe] final case class JsonDecimal(input: String) extends BiggerDecimalJsonNumber(input) {
private[circe] lazy val toBiggerDecimal: BiggerDecimal = {
val result = BiggerDecimal.parseBiggerDecimalUnsafe(input)
if (result.eq(null)) {
throw new NumberFormatException("For input string \"" + input + "\"")
} else result
}
final def toDouble: Double = java.lang.Double.parseDouble(input)
final def toFloat: Float = java.lang.Float.parseFloat(input)
}
private[circe] final case class JsonBiggerDecimal(value: BiggerDecimal, input: String)
extends BiggerDecimalJsonNumber(input) {
private[circe] def toBiggerDecimal: BiggerDecimal = value
final def toDouble: Double = toBiggerDecimal.toDouble
final def toFloat: Float = toBiggerDecimal.toFloat
}
/**
* Represent a valid JSON number as a `java.math.BigDecimal`.
*/
private[circe] final case class JsonBigDecimal(value: JavaBigDecimal) extends JsonNumber {
private[circe] def toBiggerDecimal: BiggerDecimal = BiggerDecimal.fromBigDecimal(value)
final def toBigDecimal: Option[BigDecimal] = Some(new BigDecimal(value))
final def toBigInt: Option[BigInt] = toBiggerDecimal.toBigInteger.map(BigInt(_))
final def toDouble: Double = value.doubleValue
final def toFloat: Float = value.floatValue
final def toLong: Option[Long] = toBiggerDecimal.toLong
override final def toString: String = value.toString
private[circe] def appendToStringBuilder(builder: StringBuilder): Unit = builder.append(value.toString)
}
/**
* Represent a valid JSON number as a [[scala.Long]].
*/
private[circe] final case class JsonLong(value: Long) extends JsonNumber {
private[circe] def toBiggerDecimal: BiggerDecimal = BiggerDecimal.fromLong(value)
final def toBigDecimal: Option[BigDecimal] = Some(BigDecimal(value))
final def toBigInt: Option[BigInt] = Some(BigInt(value))
final def toDouble: Double = value.toDouble
final def toFloat: Float = value.toFloat
final def toLong: Option[Long] = Some(value)
override final def toString: String = java.lang.Long.toString(value)
private[circe] def appendToStringBuilder(builder: StringBuilder): Unit = builder.append(value)
}
/**
* Represent a valid JSON number as a [[scala.Double]].
*/
private[circe] final case class JsonDouble(value: Double) extends JsonNumber {
private[circe] def toBiggerDecimal: BiggerDecimal = BiggerDecimal.fromDoubleUnsafe(value)
private[this] def toJavaBigDecimal = JavaBigDecimal.valueOf(value)
final def toBigDecimal: Option[BigDecimal] = Some(toJavaBigDecimal)
final def toBigInt: Option[BigInt] = {
val asBigDecimal = toJavaBigDecimal
if (JsonNumber.bigDecimalIsWhole(asBigDecimal)) Some(new BigInt(asBigDecimal.toBigInteger)) else None
}
final def toDouble: Double = value
final def toFloat: Float = value.toFloat
final def toLong: Option[Long] = {
val asBigDecimal = toJavaBigDecimal
if (JsonNumber.bigDecimalIsValidLong(asBigDecimal)) Some(asBigDecimal.longValue) else None
}
override final def toString: String = java.lang.Double.toString(value)
private[circe] def appendToStringBuilder(builder: StringBuilder): Unit = builder.append(value)
}
/**
* Represent a valid JSON number as a [[scala.Float]].
*/
private[circe] final case class JsonFloat(value: Float) extends JsonNumber {
private[circe] def toBiggerDecimal: BiggerDecimal = BiggerDecimal.fromFloat(value)
private[this] def toJavaBigDecimal = new JavaBigDecimal(java.lang.Float.toString(value))
final def toBigDecimal: Option[BigDecimal] = Some(toJavaBigDecimal)
final def toBigInt: Option[BigInt] = {
val asBigDecimal = toJavaBigDecimal
if (JsonNumber.bigDecimalIsWhole(asBigDecimal)) Some(new BigInt(asBigDecimal.toBigInteger)) else None
}
// Don't use `value.toFloat` due to floating point errors.
final def toDouble: Double = toJavaBigDecimal.doubleValue
final def toFloat: Float = value
final def toLong: Option[Long] = {
val asBigDecimal = toJavaBigDecimal
if (JsonNumber.bigDecimalIsValidLong(asBigDecimal)) Some(asBigDecimal.longValue) else None
}
override final def toString: String = java.lang.Float.toString(value)
private[circe] def appendToStringBuilder(builder: StringBuilder): Unit = builder.append(value)
}
/**
* Constructors, type class instances, and other utilities for [[JsonNumber]].
*/
object JsonNumber {
/**
* Return a `JsonNumber` whose value is the valid JSON number in `value`.
*
* @note This value is ''not'' verified to be a valid JSON string. It is assumed that `value` is a
* valid JSON number, according to the JSON specification. If the value is invalid the behavior is
* undefined. This operation is provided for use in situations where the validity of the input has
* already been verified.
*/
final def fromDecimalStringUnsafe(value: String): JsonNumber = JsonDecimal(value)
/**
* Return a `JsonNumber` whose value is the valid integral JSON number in `value`.
*
* @note This value is ''not'' verified to be a valid JSON string. It is assumed that `value` is a
* valid JSON number, according to the JSON specification. If the value is invalid the behavior is
* undefined. This operation is provided for use in situations where the validity of the input has
* already been verified.
*/
final def fromIntegralStringUnsafe(value: String): JsonNumber =
if (!BiggerDecimal.integralIsValidLong(value)) JsonDecimal(value)
else {
val longValue = java.lang.Long.parseLong(value)
if (value.charAt(0) == '-' && longValue == 0L) JsonDecimal(value) else JsonLong(longValue)
}
final def fromString(value: String): Option[JsonNumber] = {
val result = BiggerDecimal.parseBiggerDecimalUnsafe(value)
if (result.eq(null)) None else Some(JsonBiggerDecimal(result, value))
}
private[this] val bigDecimalMinLong: JavaBigDecimal = new JavaBigDecimal(Long.MinValue)
private[this] val bigDecimalMaxLong: JavaBigDecimal = new JavaBigDecimal(Long.MaxValue)
private[circe] def bigDecimalIsWhole(value: JavaBigDecimal): Boolean =
value.signum == 0 || value.scale <= 0 || value.stripTrailingZeros.scale <= 0
private[circe] def bigDecimalIsValidLong(value: JavaBigDecimal): Boolean =
bigDecimalIsWhole(value) && value.compareTo(bigDecimalMinLong) >= 0 && value.compareTo(bigDecimalMaxLong) <= 0
implicit final val eqJsonNumber: Eq[JsonNumber] = Eq.instance {
case (JsonLong(x), JsonLong(y)) => x == y
case (JsonDouble(x), JsonDouble(y)) => java.lang.Double.compare(x, y) == 0
case (JsonFloat(x), JsonFloat(y)) => java.lang.Float.compare(x, y) == 0
case (JsonBigDecimal(x), JsonBigDecimal(y)) => x.compareTo(y) == 0
case (a, b) => a.toBiggerDecimal == b.toBiggerDecimal
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy