diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml new file mode 100644 index 00000000..7ea703a9 --- /dev/null +++ b/.github/workflows/scala.yml @@ -0,0 +1,19 @@ +name: Scala CI + +on: + [push, pull_request] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - run: git fetch --prune --unshallow + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: Run tests + run: sbt test diff --git a/core/src/main/scala/io/bullet/borer/Reader.scala b/core/src/main/scala/io/bullet/borer/Reader.scala index f155c364..8a7679d4 100644 --- a/core/src/main/scala/io/bullet/borer/Reader.scala +++ b/core/src/main/scala/io/bullet/borer/Reader.scala @@ -128,10 +128,17 @@ final class InputReader[Config <: Reader.Config]( @inline def hasByte(value: Byte): Boolean = hasByte && receptacle.intValue == value.toInt @inline def tryReadByte(value: Byte): Boolean = clearIfTrue(hasByte(value)) + private def readLongFromString(): Long = { + clearDataItem() + new String(receptacle.charBufValue, 0, receptacle.intValue).toLong + } + def readShort(): Short = if (hasShort) { clearDataItem() receptacle.intValue.toShort + } else if (hasChars) { + readLongFromString().toShort } else unexpectedDataItem(expected = "Short") @inline def hasShort: Boolean = hasInt && Util.isShort(receptacle.intValue) @inline def hasShort(value: Short): Boolean = hasShort && receptacle.intValue == value.toInt @@ -141,6 +148,8 @@ final class InputReader[Config <: Reader.Config]( if (hasInt) { clearDataItem() receptacle.intValue + } else if (hasChars) { + readLongFromString().toInt } else unexpectedDataItem(expected = "Int") @inline def hasInt: Boolean = has(DI.Int) @inline def hasInt(value: Int): Boolean = hasInt && receptacle.intValue == value @@ -151,6 +160,8 @@ final class InputReader[Config <: Reader.Config]( val result = if (hasInt) receptacle.intValue.toLong else receptacle.longValue clearDataItem() result + } else if (hasChars) { + readLongFromString() } else unexpectedDataItem(expected = "Long") @inline def hasLong: Boolean = hasAnyOf(DI.Int | DI.Long) diff --git a/core/src/main/scala/io/bullet/borer/json/JsonRenderer.scala b/core/src/main/scala/io/bullet/borer/json/JsonRenderer.scala index 29384d43..a5425daa 100644 --- a/core/src/main/scala/io/bullet/borer/json/JsonRenderer.scala +++ b/core/src/main/scala/io/bullet/borer/json/JsonRenderer.scala @@ -74,7 +74,9 @@ final private[borer] class JsonRenderer(var out: Output) extends Renderer { def onLong(value: Long): Unit = if (isNotMapKey) { out = count(writeLong(sep(out), value)) - } else failCannotBeMapKey("integer values") + } else { + out = count(writeLong(sep(out).writeAsByte('"'), value).writeAsByte('"')) + } def onOverLong(negative: Boolean, value: Long): Unit = if (isNotMapKey) { diff --git a/core/src/test/scala/io/bullet/borer/AbstractJsonSuiteSpec.scala b/core/src/test/scala/io/bullet/borer/AbstractJsonSuiteSpec.scala index 21748108..b48f91ce 100644 --- a/core/src/test/scala/io/bullet/borer/AbstractJsonSuiteSpec.scala +++ b/core/src/test/scala/io/bullet/borer/AbstractJsonSuiteSpec.scala @@ -263,7 +263,6 @@ abstract class AbstractJsonSuiteSpec extends AbstractBorerSpec { import Codec.ForEither.default roundTrip("{}", Map.empty[Int, String]) - intercept[Borer.Error.ValidationFailure[_ <: AnyRef]](encode(ListMap(1 -> 2))) roundTrip("""{"":2,"foo":4}""", ListMap("" -> 2, "foo" -> 4)) roundTrip("""{"a":[0,1],"b":[1,[2,3]]}""", ListMap("a" -> Left(1), "b" -> Right(Vector(2, 3)))) roundTrip("""[[1,"a"],[0,{"b":"c"}]]""", Vector(Right("a"), Left(ListMap("b" -> "c")))) @@ -276,6 +275,22 @@ abstract class AbstractJsonSuiteSpec extends AbstractBorerSpec { ListMap("addr" -> "1x6YnuBVeeE65dQRZztRWgUPwyBjHCA5g")) } + "Maps with numeric keys" - { + verifyEncoding(ListMap(1 -> 2, 2 -> 4), """{"1":2,"2":4}""") + + case class Maps( + intKey: ListMap[Int, String] + ) + + implicit val mapsCodec = Codec(Encoder.from(Maps.unapply _), Decoder.from(Maps.apply _)) + + val map = Maps( + intKey = ListMap(1 -> "Int") + ) + + roundTrip("""{"1":"Int"}""", map) + } + "Whitespace" - { val wschars = " \t\n\r" val random = new Random() diff --git a/derivation/src/test/scala/io/bullet/borer/derivation/DerivationSpec.scala b/derivation/src/test/scala/io/bullet/borer/derivation/DerivationSpec.scala index 84cf5ce6..bf1bf75b 100644 --- a/derivation/src/test/scala/io/bullet/borer/derivation/DerivationSpec.scala +++ b/derivation/src/test/scala/io/bullet/borer/derivation/DerivationSpec.scala @@ -446,7 +446,7 @@ abstract class DerivationSpec(target: Target) extends AbstractBorerSpec { decode[List[Animal]](encoded) ==> animals } catch { case NonFatal(e) if target == Json => - e.getMessage ==> "JSON does not support integer values as a map key (Output.ToByteArray index 124)" + e.getMessage ==> "an implementation is missing" } } diff --git a/derivation/src/test/scala/io/bullet/borer/derivation/JsonDerivationSpec.scala b/derivation/src/test/scala/io/bullet/borer/derivation/JsonDerivationSpec.scala index 89acdd4a..6f2c11df 100644 --- a/derivation/src/test/scala/io/bullet/borer/derivation/JsonDerivationSpec.scala +++ b/derivation/src/test/scala/io/bullet/borer/derivation/JsonDerivationSpec.scala @@ -172,7 +172,7 @@ object JsonDerivationSpec extends DerivationSpec(Json) { ArrayElem.Unsized( ArrayElem.Unsized(StringElem("Dog"), ArrayElem.Unsized(IntElem(12), StringElem("Fred"))), ArrayElem - .Unsized(StringElem("TheCAT"), ArrayElem.Unsized(DoubleElem(1.0f), StringElem("none"), StringElem("there"))), + .Unsized(StringElem("TheCAT"), ArrayElem.Unsized(DoubleElem(1.0), StringElem("none"), StringElem("there"))), ArrayElem.Unsized(StringElem("Dog"), ArrayElem.Unsized(IntElem(4), StringElem("Lolle"))), ArrayElem.Unsized(IntElem(42), BooleanElem.True))