Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Late number parsing #40

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 64 additions & 8 deletions ninny/src/io/github/kag0/ninny/FromJsonInstances.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ trait FromJsonInstances
with LazyLogging {
implicit val stringFromJson: FromJson[String] = FromJson.fromSome {
case string: JsonString => Success(string.value)
case json => Failure(new JsonException(s"Expected string, got $json"))
case json => Failure(new JsonException(s"Expected string, got $json"))
}

implicit val booleanFromJson: FromJson[Boolean] = FromJson.fromSome {
Expand All @@ -40,19 +40,37 @@ trait FromJsonInstances
case string: JsonString =>
Json
.parse(string.value, highPrecision = false)
.recoverWith {
case NonFatal(e) =>
Failure(
new JsonException(
s"Failed to to parse a JSON string (${string.value}) as a number: ${e.getMessage}",
e
)
.recoverWith { case NonFatal(e) =>
Failure(
new JsonException(
s"Failed to to parse a JSON string (${string.value}) as a number: ${e.getMessage}",
e
)
)
}
.flatMap(_.to[Double])
case json => Failure(new JsonException(s"Expected number, got $json"))
}

implicit val floatFromJson: FromJson[Float] =
FromJson.fromSome(_.to[Double].flatMap {
case d if d > Float.MaxValue =>
Failure(
new JsonException(
s"Expected float, got $d (too large)",
new ArithmeticException("Overflow")
)
)
case d if d < Float.MinValue =>
Failure(
new JsonException(
s"Expected float, got $d (too small)",
new ArithmeticException("Underflow")
)
)
case d => Try(d.toFloat)
})

implicit val sBigDecimalFromJson: FromJson[BigDecimal] = FromJson.fromSome {
case decimal: JsonDecimal => Success(decimal.preciseValue)
case double: JsonDouble if java.lang.Double.isFinite(double.value) =>
Expand Down Expand Up @@ -136,6 +154,44 @@ trait FromJsonInstances
case l => Try(l.toInt)
})

implicit val shortFromJson: FromJson[Short] =
FromJson.fromSome(_.to[Int].flatMap {
case l if l > Short.MaxValue =>
Failure(
new JsonException(
s"Expected short, got $l (too large)",
new ArithmeticException("Overflow")
)
)
case l if l < Short.MinValue =>
Failure(
new JsonException(
s"Expected short, got $l (too small)",
new ArithmeticException("Underflow")
)
)
case l => Try(l.toShort)
})

implicit val byteFromJson: FromJson[Byte] =
FromJson.fromSome(_.to[Short].flatMap {
case l if l > Byte.MaxValue =>
Failure(
new JsonException(
s"Expected byte, got $l (too large)",
new ArithmeticException("Overflow")
)
)
case l if l < Byte.MinValue =>
Failure(
new JsonException(
s"Expected byte, got $l (too small)",
new ArithmeticException("Underflow")
)
)
case l => Try(l.toByte)
})

implicit val arraySeqFromJson: FromJson[ArraySeq[Byte]] = FromJson.fromSome {
case JsonBlob(value) => Success(value)
case JsonString(value) =>
Expand Down
47 changes: 41 additions & 6 deletions ninny/src/io/github/kag0/ninny/ast/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import scala.language.dynamics
import scala.collection.immutable._
import scala.collection.compat._
import scala.collection.compat.immutable.ArraySeq
import scala.reflect.runtime.universe._

package object ast {

Expand Down Expand Up @@ -87,7 +88,9 @@ package object ast {
case class JsonBlob(value: ArraySeq[Byte]) extends AnyVal with JsonValue

sealed trait JsonNumber extends Any with JsonValue {
def value: Double
// def value: Double
// def preciseValue: BigDecimal
// def as[N: Numeric]: N

override def equals(obj: Any) =
this match {
Expand All @@ -112,12 +115,44 @@ package object ast {
def unapply(json: JsonNumber) = Some(json.value)
}

case class JsonDouble(value: Double) extends AnyVal with JsonNumber
private[ninny] case class JsonNumberString(stringValue: String)
extends JsonDecimal
with JsonDouble {
lazy val value: Double = stringValue.toDouble
lazy val preciseValue: BigDecimal = BigDecimal(stringValue)
def as[N: Numeric] = ???
def as[N: Numeric: TypeTag] = if (typeTag[N] == typeTag[Double]) value
else if (typeTag[N] == typeTag[BigDecimal]) preciseValue
else implicitly[Numeric[N]].parseString(stringValue)
/*
if (N =:= Double) value
else if (N =:= BigDecimal) preciseValue
else if (N =:= java.math.BigDecimal) preciseValue.java
else implicitly[Numeric[N]].parseString(stringValue)
*/

case class JsonDecimal(preciseValue: BigDecimal)
extends AnyVal
with JsonNumber {
def value = preciseValue.doubleValue
}

trait JsonDouble extends Any with JsonNumber {
def value: Double
}
object JsonDouble {
def apply(value: Double): JsonDouble = JsonDoubleWrapper(value)
def unapply(value: JsonDouble) = Some(value.value)
private case class JsonDoubleWrapper(value: Double)
extends AnyVal
with JsonDouble
}

trait JsonDecimal extends Any with JsonNumber {
def preciseValue: BigDecimal
}
object JsonDecimal {
def apply(value: BigDecimal): JsonDecimal = JsonDecimalWrapper(value)
def unapply(value: JsonDecimal) = Some(value.preciseValue)
private case class JsonDecimalWrapper(preciseValue: BigDecimal)
extends AnyVal
with JsonDecimal
}

sealed trait JsonBoolean extends JsonValue {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.github.plokhotnyuk.jsoniter_scala.core.{
JsonReader,
JsonWriter
}
import java.nio.charset.StandardCharsets

object NinnyJsonValueCodec extends JsonValueCodec[JsonValue] {
def decodeValue(in: JsonReader, default: JsonValue) = ???
Expand All @@ -14,6 +15,8 @@ object NinnyJsonValueCodec extends JsonValueCodec[JsonValue] {
case string: JsonString => out.writeVal(string.value)
case JsonFalse => out.writeVal(false)
case JsonTrue => out.writeVal(true)
case string: JsonNumberString =>
out.writeRawVal(string.stringValue.getBytes(StandardCharsets.UTF_8))
case decimal: JsonDecimal =>
if (decimal.preciseValue.precision == 0)
out.writeVal(decimal.preciseValue.toBigInt)
Expand All @@ -29,10 +32,9 @@ object NinnyJsonValueCodec extends JsonValueCodec[JsonValue] {
out.writeNonEscapedAsciiVal(double.value.toString)
case obj: JsonObject =>
out.writeObjectStart()
obj.values.foreach {
case (k, v) =>
out.writeKey(k)
encodeValue(v, out)
obj.values.foreach { case (k, v) =>
out.writeKey(k)
encodeValue(v, out)
}
out.writeObjectEnd()
case array: JsonArray =>
Expand Down
30 changes: 29 additions & 1 deletion ninny/test/src-2/io/github/kag0/ninny/JsonSpec.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package io.github.kag0.ninny
package io.github.kag0.test.ninny

import java.time.temporal.ChronoUnit
import java.time.{Instant, OffsetDateTime, ZonedDateTime}
import java.util.NoSuchElementException

import io.github.kag0.ninny._
import io.github.kag0.ninny.ast._
import org.scalatest._
import org.scalatest.flatspec._
Expand Down Expand Up @@ -597,6 +598,27 @@ class JsonSpec
float.toSomeJson shouldEqual JsonDouble(5.5)
}

it should "read any primitive number" in {
val long: Long = 5
val int: Int = 5
val byte: Byte = 5
val double: Double = 5.5
val float: Float = 5.5f

long.toSomeJson shouldEqual JsonDouble(5)
int.toSomeJson shouldEqual JsonDouble(5)
byte.toSomeJson shouldEqual JsonDouble(5)
double.toSomeJson shouldEqual JsonDouble(double)
float.toSomeJson shouldEqual JsonDouble(5.5)

JsonDouble(1).to[Long].success.value shouldEqual 1
JsonDouble(1).to[Int].success.value shouldEqual 1
JsonDouble(1).to[Byte].success.value shouldEqual 1
JsonDouble(1).to[Short].success.value shouldEqual 1
JsonDouble(1.5).to[Double].success.value shouldEqual 1.5
JsonDouble(1.5).to[Float].success.value shouldEqual 1.5
}

it should "write big numbers with high precision" in {
val i = BigInt(123)
val d = BigDecimal("123.456")
Expand Down Expand Up @@ -738,4 +760,10 @@ class JsonSpec
js shouldEqual obj("baz" -> "bar", "bop" -> 1)
fromJs shouldEqual example
}

val v: JsonValue = ???
v match {
case JsonString(s) => s
// case JsonNumber(s) => s
}
}