From 063f571ef9016babf2ada0fdd3b21b75ba55c356 Mon Sep 17 00:00:00 2001 From: kyri-petrou <67301607+kyri-petrou@users.noreply.github.com> Date: Wed, 8 Jul 2026 12:17:36 +0300 Subject: [PATCH 1/2] Update jsoniter number parser implementation to match jsoniter-circe --- .../caliban/interop/jsoniter/jsoniter.scala | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/core/src/main/scala/caliban/interop/jsoniter/jsoniter.scala b/core/src/main/scala/caliban/interop/jsoniter/jsoniter.scala index f154271e1..ab445b049 100644 --- a/core/src/main/scala/caliban/interop/jsoniter/jsoniter.scala +++ b/core/src/main/scala/caliban/interop/jsoniter/jsoniter.scala @@ -205,26 +205,24 @@ private[caliban] object ValueJsoniter { private val numberParser: JsonReader => Value = in => { in.setMark() - var digits = 0 + var b = in.nextByte() if (b == '-') b = in.nextByte() - try - while (b >= '0' && b <= '9') { + var digits = 0 + while ( + (b >= '0' && b <= '9') && { digits += 1 - b = in.nextByte() + in.hasRemaining() } - catch { - case _: JsonReaderException => // ignore the end of input error for now - } + ) b = in.nextByte() in.rollbackToMark() if ((b | 0x20) != 'e' && b != '.') { - if (digits < 19) { - if (digits < 10) Value.IntValue.IntNumber(in.readInt()) - else Value.IntValue.LongNumber(in.readLong()) - } else { + if (digits < 10) Value.IntValue.IntNumber(in.readInt()) + else if (digits < 19) Value.IntValue.LongNumber(in.readLong()) + else { val x = in.readBigInt(null) - if (x.bitLength < 64) Value.IntValue.LongNumber(x.longValue) + if (x.isValidLong) Value.IntValue.LongNumber(x.longValue) else Value.IntValue.BigIntNumber(x.bigInteger) } } else Value.FloatValue.BigDecimalNumber(in.readBigDecimal(null).bigDecimal) From c64e9826a4ce5a959c11fc06c6faae7a5c1eec76 Mon Sep 17 00:00:00 2001 From: kyri-petrou <67301607+kyri-petrou@users.noreply.github.com> Date: Sat, 25 Jul 2026 09:46:05 +0300 Subject: [PATCH 2/2] Use `readNumber` from jsoniter to parse json numbers --- .../caliban/interop/jsoniter/jsoniter.scala | 41 +++++++------------ .../jsoniter/GraphQLRequestJsoniterSpec.scala | 14 ++++--- 2 files changed, 24 insertions(+), 31 deletions(-) diff --git a/core/src/main/scala/caliban/interop/jsoniter/jsoniter.scala b/core/src/main/scala/caliban/interop/jsoniter/jsoniter.scala index ab445b049..e2d5cd13b 100644 --- a/core/src/main/scala/caliban/interop/jsoniter/jsoniter.scala +++ b/core/src/main/scala/caliban/interop/jsoniter/jsoniter.scala @@ -123,7 +123,7 @@ private[caliban] object ValueJsoniter { StringValue(in.readString(null)) case x if x == '-' || (x >= '0' && x <= '9') => in.rollbackToken() - numberParser(in) + parseNumber(in) case 'n' => in.readNullOrError(NullValue, "unexpected JSON value") case 'f' | 't' => @@ -169,7 +169,7 @@ private[caliban] object ValueJsoniter { StringValue(in.readString(null)) case x if x == '-' || (x >= '0' && x <= '9') => in.rollbackToken() - numberParser(in) + parseNumber(in) case 'n' => in.readNullOrError(NullValue, "unexpected JSON value") case 'f' | 't' => @@ -203,30 +203,19 @@ private[caliban] object ValueJsoniter { in.decodeError(s"unexpected token $c") } - private val numberParser: JsonReader => Value = in => { - in.setMark() - - var b = in.nextByte() - if (b == '-') b = in.nextByte() - var digits = 0 - while ( - (b >= '0' && b <= '9') && { - digits += 1 - in.hasRemaining() - } - ) b = in.nextByte() - in.rollbackToMark() - - if ((b | 0x20) != 'e' && b != '.') { - if (digits < 10) Value.IntValue.IntNumber(in.readInt()) - else if (digits < 19) Value.IntValue.LongNumber(in.readLong()) - else { - val x = in.readBigInt(null) - if (x.isValidLong) Value.IntValue.LongNumber(x.longValue) - else Value.IntValue.BigIntNumber(x.bigInteger) - } - } else Value.FloatValue.BigDecimalNumber(in.readBigDecimal(null).bigDecimal) - } + private def parseNumber(in: JsonReader): Value = + in.readNumber(null) match { + case l: java.lang.Long => + val asLong = l.longValue() + val asInt = asLong.toInt + // Check if the number can fit in an Int (most common case) + if (asInt.toLong == asLong) Value.IntValue.IntNumber(asInt) + else Value.IntValue.LongNumber(asLong) + case bd: java.math.BigDecimal => + Value.FloatValue.BigDecimalNumber(bd) + case bi: java.math.BigInteger => + Value.IntValue.BigIntNumber(bi) + } val inputValueCodec: JsonValueCodec[InputValue] = new JsonValueCodec[InputValue] { override def decodeValue(in: JsonReader, default: InputValue): InputValue = diff --git a/core/src/test/scala/caliban/interop/jsoniter/GraphQLRequestJsoniterSpec.scala b/core/src/test/scala/caliban/interop/jsoniter/GraphQLRequestJsoniterSpec.scala index 9a769ecc3..c498ba39b 100644 --- a/core/src/test/scala/caliban/interop/jsoniter/GraphQLRequestJsoniterSpec.scala +++ b/core/src/test/scala/caliban/interop/jsoniter/GraphQLRequestJsoniterSpec.scala @@ -11,7 +11,7 @@ object GraphQLRequestJsoniterSpec extends ZIOSpecDefault { suite("GraphQLRequestJsoniterSpec")( test("can be parsed from JSON by jsoniter") { val request = - """{"query": "{}", "operationName": "op", "variables": {"hello":"world","answer":42,"isAwesome":true, "name": null}}""" + """{"query": "{}", "operationName": "op", "variables": {"hello":"world","intValue":42,"longValue":99999999999,"bigIntValue":9223372036854775810,"decimalValue":1.1,"bigDecimalValue":4.4028235E38,"isAwesome":true, "name": null}}""" assert(readFromString[GraphQLRequest](request))( equalTo( GraphQLRequest( @@ -19,10 +19,14 @@ object GraphQLRequestJsoniterSpec extends ZIOSpecDefault { operationName = Some("op"), variables = Some( Map( - "hello" -> Value.StringValue("world"), - "answer" -> Value.IntValue(42), - "isAwesome" -> Value.BooleanValue(true), - "name" -> Value.NullValue + "hello" -> Value.StringValue("world"), + "intValue" -> Value.IntValue.IntNumber(42), + "longValue" -> Value.IntValue.LongNumber(99999999999L), + "bigIntValue" -> Value.IntValue.BigIntNumber(BigInt("9223372036854775810")), + "decimalValue" -> Value.FloatValue(BigDecimal("1.1")), + "bigDecimalValue" -> Value.FloatValue(BigDecimal("4.4028235E38")), + "isAwesome" -> Value.BooleanValue(true), + "name" -> Value.NullValue ) ) )