From fec8e2c1a69906662feafaab5bdcf8f907b75197 Mon Sep 17 00:00:00 2001 From: Andriy Plokhotnyuk Date: Sun, 2 Feb 2025 19:46:33 +0100 Subject: [PATCH] Add support of `immutable.ArraySeq` + more inlinings (#1279) --- .../zio/json}/JsonCodecVersionSpecific.scala | 0 .../zio/json/JsonDecoderVersionSpecific.scala | 0 .../zio/json/JsonEncoderVersionSpecific.scala | 0 .../zio/json/JsonCodecVersionSpecific.scala | 8 ++ .../zio/json/JsonDecoderVersionSpecific.scala | 20 +++++ .../zio/json/JsonEncoderVersionSpecific.scala | 23 +++++ .../zio/json/JsonCodecVersionSpecific.scala | 4 + .../zio/json/JsonDecoderVersionSpecific.scala | 15 +++- .../zio/json/JsonEncoderVersionSpecific.scala | 18 +++- .../src/main/scala/zio/json/JsonCodec.scala | 2 + .../src/main/scala/zio/json/JsonDecoder.scala | 2 +- .../src/main/scala/zio/json/JsonEncoder.scala | 85 ++++++------------- .../main/scala/zio/json/internal/lexer.scala | 28 ++---- .../zio/json/CodecVersionSpecificSpec.scala | 18 ++++ .../zio/json/DecoderVersionSpecificSpec.scala | 30 +++++++ .../zio/json/EncoderVesionSpecificSpec.scala | 31 +++++++ .../zio/json/CodecVersionSpecificSpec.scala | 18 ++++ .../zio/json/DecoderVersionSpecificSpec.scala | 30 +++++++ .../zio/json/EncoderVesionSpecificSpec.scala | 31 +++++++ 19 files changed, 282 insertions(+), 81 deletions(-) rename zio-json/shared/src/main/{scala-2.x => scala-2.12/zio/json}/JsonCodecVersionSpecific.scala (100%) rename zio-json/shared/src/main/{scala-2.x => scala-2.12}/zio/json/JsonDecoderVersionSpecific.scala (100%) rename zio-json/shared/src/main/{scala-2.x => scala-2.12}/zio/json/JsonEncoderVersionSpecific.scala (100%) create mode 100644 zio-json/shared/src/main/scala-2.13/zio/json/JsonCodecVersionSpecific.scala create mode 100644 zio-json/shared/src/main/scala-2.13/zio/json/JsonDecoderVersionSpecific.scala create mode 100644 zio-json/shared/src/main/scala-2.13/zio/json/JsonEncoderVersionSpecific.scala create mode 100644 zio-json/shared/src/test/scala-2.13/zio/json/CodecVersionSpecificSpec.scala create mode 100644 zio-json/shared/src/test/scala-2.13/zio/json/DecoderVersionSpecificSpec.scala create mode 100644 zio-json/shared/src/test/scala-2.13/zio/json/EncoderVesionSpecificSpec.scala create mode 100644 zio-json/shared/src/test/scala-3/zio/json/CodecVersionSpecificSpec.scala create mode 100644 zio-json/shared/src/test/scala-3/zio/json/DecoderVersionSpecificSpec.scala create mode 100644 zio-json/shared/src/test/scala-3/zio/json/EncoderVesionSpecificSpec.scala diff --git a/zio-json/shared/src/main/scala-2.x/JsonCodecVersionSpecific.scala b/zio-json/shared/src/main/scala-2.12/zio/json/JsonCodecVersionSpecific.scala similarity index 100% rename from zio-json/shared/src/main/scala-2.x/JsonCodecVersionSpecific.scala rename to zio-json/shared/src/main/scala-2.12/zio/json/JsonCodecVersionSpecific.scala diff --git a/zio-json/shared/src/main/scala-2.x/zio/json/JsonDecoderVersionSpecific.scala b/zio-json/shared/src/main/scala-2.12/zio/json/JsonDecoderVersionSpecific.scala similarity index 100% rename from zio-json/shared/src/main/scala-2.x/zio/json/JsonDecoderVersionSpecific.scala rename to zio-json/shared/src/main/scala-2.12/zio/json/JsonDecoderVersionSpecific.scala diff --git a/zio-json/shared/src/main/scala-2.x/zio/json/JsonEncoderVersionSpecific.scala b/zio-json/shared/src/main/scala-2.12/zio/json/JsonEncoderVersionSpecific.scala similarity index 100% rename from zio-json/shared/src/main/scala-2.x/zio/json/JsonEncoderVersionSpecific.scala rename to zio-json/shared/src/main/scala-2.12/zio/json/JsonEncoderVersionSpecific.scala diff --git a/zio-json/shared/src/main/scala-2.13/zio/json/JsonCodecVersionSpecific.scala b/zio-json/shared/src/main/scala-2.13/zio/json/JsonCodecVersionSpecific.scala new file mode 100644 index 000000000..e6d8f0f9b --- /dev/null +++ b/zio-json/shared/src/main/scala-2.13/zio/json/JsonCodecVersionSpecific.scala @@ -0,0 +1,8 @@ +package zio.json + +import scala.collection.immutable + +trait JsonCodecVersionSpecific { + implicit def arraySeq[A: JsonEncoder: JsonDecoder: reflect.ClassTag]: JsonCodec[immutable.ArraySeq[A]] = + JsonCodec(JsonEncoder.arraySeq[A], JsonDecoder.arraySeq[A]) +} diff --git a/zio-json/shared/src/main/scala-2.13/zio/json/JsonDecoderVersionSpecific.scala b/zio-json/shared/src/main/scala-2.13/zio/json/JsonDecoderVersionSpecific.scala new file mode 100644 index 000000000..eb0ffc9e0 --- /dev/null +++ b/zio-json/shared/src/main/scala-2.13/zio/json/JsonDecoderVersionSpecific.scala @@ -0,0 +1,20 @@ +package zio.json + +import zio.json.JsonDecoder.JsonError +import zio.json.internal.RetractReader + +import scala.collection.immutable + +private[json] trait JsonDecoderVersionSpecific { + implicit def arraySeq[A: JsonDecoder: reflect.ClassTag]: JsonDecoder[immutable.ArraySeq[A]] = + new CollectionJsonDecoder[immutable.ArraySeq[A]] { + private[this] val arrayDecoder = JsonDecoder.array[A] + + override def unsafeDecodeMissing(trace: List[JsonError]): immutable.ArraySeq[A] = immutable.ArraySeq.empty + + def unsafeDecode(trace: List[JsonError], in: RetractReader): immutable.ArraySeq[A] = + immutable.ArraySeq.unsafeWrapArray(arrayDecoder.unsafeDecode(trace, in)) + } +} + +private[json] trait DecoderLowPriorityVersionSpecific diff --git a/zio-json/shared/src/main/scala-2.13/zio/json/JsonEncoderVersionSpecific.scala b/zio-json/shared/src/main/scala-2.13/zio/json/JsonEncoderVersionSpecific.scala new file mode 100644 index 000000000..693472327 --- /dev/null +++ b/zio-json/shared/src/main/scala-2.13/zio/json/JsonEncoderVersionSpecific.scala @@ -0,0 +1,23 @@ +package zio.json + +import zio.json.ast.Json +import zio.json.internal.Write + +import scala.collection.immutable + +private[json] trait JsonEncoderVersionSpecific { + implicit def arraySeq[A: JsonEncoder: scala.reflect.ClassTag]: JsonEncoder[immutable.ArraySeq[A]] = + new JsonEncoder[immutable.ArraySeq[A]] { + private[this] val arrayEnc = JsonEncoder.array[A] + + override def isEmpty(as: immutable.ArraySeq[A]): Boolean = as.isEmpty + + def unsafeEncode(as: immutable.ArraySeq[A], indent: Option[Int], out: Write): Unit = + arrayEnc.unsafeEncode(as.unsafeArray.asInstanceOf[Array[A]], indent, out) + + override final def toJsonAST(as: immutable.ArraySeq[A]): Either[String, Json] = + arrayEnc.toJsonAST(as.unsafeArray.asInstanceOf[Array[A]]) + } +} + +private[json] trait EncoderLowPriorityVersionSpecific diff --git a/zio-json/shared/src/main/scala-3/zio/json/JsonCodecVersionSpecific.scala b/zio-json/shared/src/main/scala-3/zio/json/JsonCodecVersionSpecific.scala index f9c180f0a..0be993585 100644 --- a/zio-json/shared/src/main/scala-3/zio/json/JsonCodecVersionSpecific.scala +++ b/zio-json/shared/src/main/scala-3/zio/json/JsonCodecVersionSpecific.scala @@ -1,6 +1,10 @@ package zio.json +import scala.collection.immutable + private[json] trait JsonCodecVersionSpecific { inline def derived[A: deriving.Mirror.Of](using config: JsonCodecConfiguration): JsonCodec[A] = DeriveJsonCodec.gen[A] + implicit def arraySeq[A: JsonEncoder: JsonDecoder: reflect.ClassTag]: JsonCodec[immutable.ArraySeq[A]] = + JsonCodec(JsonEncoder.arraySeq[A], JsonDecoder.arraySeq[A]) } diff --git a/zio-json/shared/src/main/scala-3/zio/json/JsonDecoderVersionSpecific.scala b/zio-json/shared/src/main/scala-3/zio/json/JsonDecoderVersionSpecific.scala index a70233ec5..4cb0edb13 100644 --- a/zio-json/shared/src/main/scala-3/zio/json/JsonDecoderVersionSpecific.scala +++ b/zio-json/shared/src/main/scala-3/zio/json/JsonDecoderVersionSpecific.scala @@ -1,15 +1,28 @@ package zio.json +import zio.json.JsonDecoder.JsonError +import zio.json.internal.RetractReader + +import scala.collection.immutable import scala.compiletime.* import scala.compiletime.ops.any.IsConst private[json] trait JsonDecoderVersionSpecific { inline def derived[A: deriving.Mirror.Of](using config: JsonCodecConfiguration): JsonDecoder[A] = DeriveJsonDecoder.gen[A] + + implicit def arraySeq[A: JsonDecoder: reflect.ClassTag]: JsonDecoder[immutable.ArraySeq[A]] = + new CollectionJsonDecoder[immutable.ArraySeq[A]] { + private[this] val arrayDecoder = JsonDecoder.array[A] + + override def unsafeDecodeMissing(trace: List[JsonError]): immutable.ArraySeq[A] = immutable.ArraySeq.empty + + def unsafeDecode(trace: List[JsonError], in: RetractReader): immutable.ArraySeq[A] = + immutable.ArraySeq.unsafeWrapArray(arrayDecoder.unsafeDecode(trace, in)) + } } trait DecoderLowPriorityVersionSpecific { - inline given unionOfStringEnumeration[T](using IsUnionOf[String, T]): JsonDecoder[T] = val values = UnionDerivation.constValueUnionTuple[String, T] JsonDecoder.string.mapOrFail { diff --git a/zio-json/shared/src/main/scala-3/zio/json/JsonEncoderVersionSpecific.scala b/zio-json/shared/src/main/scala-3/zio/json/JsonEncoderVersionSpecific.scala index 82932a7cf..e9b290068 100644 --- a/zio-json/shared/src/main/scala-3/zio/json/JsonEncoderVersionSpecific.scala +++ b/zio-json/shared/src/main/scala-3/zio/json/JsonEncoderVersionSpecific.scala @@ -1,14 +1,30 @@ package zio.json +import zio.json.ast.Json +import zio.json.internal.Write + +import scala.collection.immutable import scala.compiletime.ops.any.IsConst private[json] trait JsonEncoderVersionSpecific { inline def derived[A: deriving.Mirror.Of](using config: JsonCodecConfiguration): JsonEncoder[A] = DeriveJsonEncoder.gen[A] + + implicit def arraySeq[A: JsonEncoder: scala.reflect.ClassTag]: JsonEncoder[immutable.ArraySeq[A]] = + new JsonEncoder[immutable.ArraySeq[A]] { + private[this] val arrayEnc = JsonEncoder.array[A] + + override def isEmpty(as: immutable.ArraySeq[A]): Boolean = as.isEmpty + + def unsafeEncode(as: immutable.ArraySeq[A], indent: Option[Int], out: Write): Unit = + arrayEnc.unsafeEncode(as.unsafeArray.asInstanceOf[Array[A]], indent, out) + + override final def toJsonAST(as: immutable.ArraySeq[A]): Either[String, Json] = + arrayEnc.toJsonAST(as.unsafeArray.asInstanceOf[Array[A]]) + } } private[json] trait EncoderLowPriorityVersionSpecific { - inline given unionOfStringEnumeration[T](using IsUnionOf[String, T]): JsonEncoder[T] = JsonEncoder.string.asInstanceOf[JsonEncoder[T]] } diff --git a/zio-json/shared/src/main/scala/zio/json/JsonCodec.scala b/zio-json/shared/src/main/scala/zio/json/JsonCodec.scala index 25d368f73..ccd6ece13 100644 --- a/zio-json/shared/src/main/scala/zio/json/JsonCodec.scala +++ b/zio-json/shared/src/main/scala/zio/json/JsonCodec.scala @@ -120,6 +120,8 @@ object JsonCodec extends GeneratedTupleCodecs with CodecLowPriority0 with JsonCo } private[json] trait CodecLowPriority0 extends CodecLowPriority1 { this: JsonCodec.type => + implicit def array[A: JsonEncoder: JsonDecoder: reflect.ClassTag]: JsonCodec[Array[A]] = + JsonCodec(JsonEncoder.array[A], JsonDecoder.array[A]) implicit def chunk[A: JsonEncoder: JsonDecoder]: JsonCodec[Chunk[A]] = JsonCodec(JsonEncoder.chunk[A], JsonDecoder.chunk[A]) diff --git a/zio-json/shared/src/main/scala/zio/json/JsonDecoder.scala b/zio-json/shared/src/main/scala/zio/json/JsonDecoder.scala index 8541bb27a..e6183691d 100644 --- a/zio-json/shared/src/main/scala/zio/json/JsonDecoder.scala +++ b/zio-json/shared/src/main/scala/zio/json/JsonDecoder.scala @@ -458,7 +458,7 @@ private[json] trait MappedJsonDecoder[A] extends JsonDecoder[A] { private[json] trait DecoderLowPriority1 extends DecoderLowPriority2 { this: JsonDecoder.type => - implicit def array[A](implicit A: JsonDecoder[A], ct: reflect.ClassTag[A]): JsonDecoder[Array[A]] = + implicit def array[A](implicit A: JsonDecoder[A], classTag: reflect.ClassTag[A]): JsonDecoder[Array[A]] = new CollectionJsonDecoder[Array[A]] { override def unsafeDecodeMissing(trace: List[JsonError]): Array[A] = Array.empty diff --git a/zio-json/shared/src/main/scala/zio/json/JsonEncoder.scala b/zio-json/shared/src/main/scala/zio/json/JsonEncoder.scala index ca09d2977..f3b0d09e7 100644 --- a/zio-json/shared/src/main/scala/zio/json/JsonEncoder.scala +++ b/zio-json/shared/src/main/scala/zio/json/JsonEncoder.scala @@ -23,7 +23,6 @@ import zio.{ Chunk, NonEmptyChunk } import java.util.UUID import scala.annotation._ import scala.collection.{ immutable, mutable } -import scala.reflect.ClassTag trait JsonEncoder[A] extends JsonEncoderPlatformSpecific[A] { self => @@ -33,7 +32,6 @@ trait JsonEncoder[A] extends JsonEncoderPlatformSpecific[A] { * user-defined function. */ final def contramap[B](f: B => A): JsonEncoder[B] = new JsonEncoder[B] { - override def unsafeEncode(b: B, indent: Option[Int], out: Write): Unit = self.unsafeEncode(f(b), indent, out) @@ -41,8 +39,7 @@ trait JsonEncoder[A] extends JsonEncoderPlatformSpecific[A] { override def isEmpty(b: B): Boolean = self.isEmpty(f(b)) - override final def toJsonAST(b: B): Either[String, Json] = - self.toJsonAST(f(b)) + override final def toJsonAST(b: B): Either[String, Json] = self.toJsonAST(f(b)) } /** @@ -114,10 +111,9 @@ trait JsonEncoder[A] extends JsonEncoderPlatformSpecific[A] { } object JsonEncoder extends GeneratedTupleEncoders with EncoderLowPriority1 with JsonEncoderVersionSpecific { - def apply[A](implicit a: JsonEncoder[A]): JsonEncoder[A] = a + @inline def apply[A](implicit a: JsonEncoder[A]): JsonEncoder[A] = a implicit val string: JsonEncoder[String] = new JsonEncoder[String] { - override def unsafeEncode(a: String, indent: Option[Int], out: Write): Unit = { out.write('"') val len = a.length @@ -134,8 +130,7 @@ object JsonEncoder extends GeneratedTupleEncoders with EncoderLowPriority1 with out.write('"') } - override final def toJsonAST(a: String): Either[String, Json] = - Right(Json.Str(a)) + override final def toJsonAST(a: String): Either[String, Json] = new Right(Json.Str(a)) private[this] def writeEncoded(a: String, out: Write): Unit = { val len = a.length @@ -157,11 +152,9 @@ object JsonEncoder extends GeneratedTupleEncoders with EncoderLowPriority1 with } out.write('"') } - } implicit val char: JsonEncoder[Char] = new JsonEncoder[Char] { - override def unsafeEncode(a: Char, indent: Option[Int], out: Write): Unit = { out.write('"') (a: @switch) match { @@ -179,15 +172,13 @@ object JsonEncoder extends GeneratedTupleEncoders with EncoderLowPriority1 with out.write('"') } - override final def toJsonAST(a: Char): Either[String, Json] = - Right(Json.Str(a.toString)) + override final def toJsonAST(a: Char): Either[String, Json] = new Right(Json.Str(a.toString)) } private[json] def explicit[A](f: A => String, g: A => Json): JsonEncoder[A] = new JsonEncoder[A] { def unsafeEncode(a: A, indent: Option[Int], out: Write): Unit = out.write(f(a)) - override final def toJsonAST(a: A): Either[String, Json] = - Right(g(a)) + override final def toJsonAST(a: A): Either[String, Json] = new Right(g(a)) } private[json] def stringify[A](f: A => String): JsonEncoder[A] = new JsonEncoder[A] { @@ -197,8 +188,7 @@ object JsonEncoder extends GeneratedTupleEncoders with EncoderLowPriority1 with out.write('"') } - override final def toJsonAST(a: A): Either[String, Json] = - Right(Json.Str(f(a))) + override final def toJsonAST(a: A): Either[String, Json] = new Right(Json.Str(f(a))) } def suspend[A](encoder0: => JsonEncoder[A]): JsonEncoder[A] = @@ -224,48 +214,36 @@ object JsonEncoder extends GeneratedTupleEncoders with EncoderLowPriority1 with explicit(_.toString, n => Json.Num(new java.math.BigDecimal(n))) implicit val scalaBigInt: JsonEncoder[BigInt] = explicit(_.toString, n => Json.Num(new java.math.BigDecimal(n.bigInteger))) - implicit val double: JsonEncoder[Double] = - explicit(SafeNumbers.toString, n => Json.Num(n)) - implicit val float: JsonEncoder[Float] = - explicit(SafeNumbers.toString, n => Json.Num(n)) + implicit val double: JsonEncoder[Double] = explicit(SafeNumbers.toString, n => Json.Num(n)) + implicit val float: JsonEncoder[Float] = explicit(SafeNumbers.toString, n => Json.Num(n)) implicit val bigDecimal: JsonEncoder[java.math.BigDecimal] = explicit(_.toString, Json.Num.apply) implicit val scalaBigDecimal: JsonEncoder[BigDecimal] = explicit(_.toString, n => Json.Num(n.bigDecimal)) implicit def option[A](implicit A: JsonEncoder[A]): JsonEncoder[Option[A]] = new JsonEncoder[Option[A]] { + def unsafeEncode(oa: Option[A], indent: Option[Int], out: Write): Unit = + if (oa eq None) out.write("null") + else A.unsafeEncode(oa.get, indent, out) - def unsafeEncode(oa: Option[A], indent: Option[Int], out: Write): Unit = oa match { - case None => out.write("null") - case Some(a) => A.unsafeEncode(a, indent, out) - } - - override def isNothing(oa: Option[A]): Boolean = - oa match { - case None => true - case Some(a) => A.isNothing(a) - } + override def isNothing(oa: Option[A]): Boolean = (oa eq None) || A.isNothing(oa.get) override final def toJsonAST(oa: Option[A]): Either[String, Json] = - oa match { - case None => Right(Json.Null) - case Some(a) => A.toJsonAST(a) - } + if (oa eq None) new Right(Json.Null) + else A.toJsonAST(oa.get) } - def bump(indent: Option[Int]): Option[Int] = indent match { - case None => None - case Some(i) => Some(i + 1) - } + def bump(indent: Option[Int]): Option[Int] = + if (indent ne None) new Some(indent.get + 1) + else indent - def pad(indent: Option[Int], out: Write): Unit = indent match { - case None => () - case Some(n) => + def pad(indent: Option[Int], out: Write): Unit = + if (indent ne None) { out.write('\n') - var i = n + var i = indent.get while (i > 0) { out.write(" ") i -= 1 } - } + } implicit def either[A, B](implicit A: JsonEncoder[A], B: JsonEncoder[B]): JsonEncoder[Either[A, B]] = new JsonEncoder[Either[A, B]] { @@ -320,12 +298,8 @@ object JsonEncoder extends GeneratedTupleEncoders with EncoderLowPriority1 with private[json] trait EncoderLowPriority1 extends EncoderLowPriority2 { this: JsonEncoder.type => - implicit def array[A](implicit - A: JsonEncoder[A], - classTag: ClassTag[A] - ): JsonEncoder[Array[A]] = + implicit def array[A](implicit A: JsonEncoder[A], classTag: scala.reflect.ClassTag[A]): JsonEncoder[Array[A]] = new JsonEncoder[Array[A]] { - override def isEmpty(as: Array[A]): Boolean = as.isEmpty def unsafeEncode(as: Array[A], indent: Option[Int], out: Write): Unit = @@ -389,17 +363,14 @@ private[json] trait EncoderLowPriority1 extends EncoderLowPriority2 { implicit def vector[A: JsonEncoder]: JsonEncoder[Vector[A]] = iterable[A, Vector] - implicit def set[A: JsonEncoder]: JsonEncoder[Set[A]] = - iterable[A, Set] + implicit def set[A: JsonEncoder]: JsonEncoder[Set[A]] = iterable[A, Set] - implicit def hashSet[A: JsonEncoder]: JsonEncoder[immutable.HashSet[A]] = - iterable[A, immutable.HashSet] + implicit def hashSet[A: JsonEncoder]: JsonEncoder[immutable.HashSet[A]] = iterable[A, immutable.HashSet] implicit def sortedSet[A: Ordering: JsonEncoder]: JsonEncoder[immutable.SortedSet[A]] = iterable[A, immutable.SortedSet] - implicit def map[K: JsonFieldEncoder, V: JsonEncoder]: JsonEncoder[Map[K, V]] = - keyValueIterable[K, V, Map] + implicit def map[K: JsonFieldEncoder, V: JsonEncoder]: JsonEncoder[Map[K, V]] = keyValueIterable[K, V, Map] implicit def hashMap[K: JsonFieldEncoder, V: JsonEncoder]: JsonEncoder[immutable.HashMap[K, V]] = keyValueIterable[K, V, immutable.HashMap] @@ -417,11 +388,8 @@ private[json] trait EncoderLowPriority1 extends EncoderLowPriority2 { private[json] trait EncoderLowPriority2 extends EncoderLowPriority3 { this: JsonEncoder.type => - implicit def iterable[A, T[X] <: Iterable[X]](implicit - A: JsonEncoder[A] - ): JsonEncoder[T[A]] = + implicit def iterable[A, T[X] <: Iterable[X]](implicit A: JsonEncoder[A]): JsonEncoder[T[A]] = new JsonEncoder[T[A]] { - override def isEmpty(as: T[A]): Boolean = as.isEmpty def unsafeEncode(as: T[A], indent: Option[Int], out: Write): Unit = @@ -471,7 +439,6 @@ private[json] trait EncoderLowPriority2 extends EncoderLowPriority3 { K: JsonFieldEncoder[K], A: JsonEncoder[A] ): JsonEncoder[T[K, A]] = new JsonEncoder[T[K, A]] { - override def isEmpty(a: T[K, A]): Boolean = a.isEmpty def unsafeEncode(kvs: T[K, A], indent: Option[Int], out: Write): Unit = diff --git a/zio-json/shared/src/main/scala/zio/json/internal/lexer.scala b/zio-json/shared/src/main/scala/zio/json/internal/lexer.scala index 5aeefdcc7..787220c56 100644 --- a/zio-json/shared/src/main/scala/zio/json/internal/lexer.scala +++ b/zio-json/shared/src/main/scala/zio/json/internal/lexer.scala @@ -38,7 +38,7 @@ object Lexer { error(s"invalid '\\$c' in string", trace) // True if we got a string (implies a retraction), False for } - def firstField(trace: List[JsonError], in: RetractReader): Boolean = + @inline def firstField(trace: List[JsonError], in: RetractReader): Boolean = (in.nextNonWhitespace(): @switch) match { case '"' => in.retract() @@ -48,7 +48,7 @@ object Lexer { } // True if we got a comma, and False for } - def nextField(trace: List[JsonError], in: OneCharReader): Boolean = + @inline def nextField(trace: List[JsonError], in: OneCharReader): Boolean = (in.nextNonWhitespace(): @switch) match { case ',' => true case '}' => false @@ -69,7 +69,7 @@ object Lexer { case c => error("',' or ']'", c, trace) } - def field(trace: List[JsonError], in: OneCharReader, matrix: StringMatrix): Int = { + @inline def field(trace: List[JsonError], in: OneCharReader, matrix: StringMatrix): Int = { val f = enumeration(trace, in, matrix) val c = in.nextNonWhitespace() if (c != ':') error("':'", c, trace) @@ -106,7 +106,7 @@ object Lexer { matrix.first(bs) } - def skipValue(trace: List[JsonError], in: RetractReader): Unit = + @noinline def skipValue(trace: List[JsonError], in: RetractReader): Unit = (in.nextNonWhitespace(): @switch) match { case 'n' | 't' => skipFixedChars(in, 3) case 'f' => skipFixedChars(in, 4) @@ -120,10 +120,11 @@ object Lexer { } def skipNumber(in: RetractReader): Unit = { - while (isNumber(in.readChar())) {} + while (isNumber(in.readChar())) () in.retract() } + // FIXME: remove in the next major version def skipString(trace: List[JsonError], in: OneCharReader): Unit = skipString(in, evenBackSlashes = true) @@ -160,7 +161,7 @@ object Lexer { else if (level != 0) skipArray(in, level - 1) } - // useful for embedded documents, e.g. CSV contained inside JSON + // FIXME: remove in the next major version def streamingString(trace: List[JsonError], in: OneCharReader): java.io.Reader = { char(trace, in, '"') new OneCharReader { @@ -357,34 +358,23 @@ object Lexer { case UnsafeNumbers.UnsafeNumber => error(s"expected a $NumberMaxBits BigDecimal", trace) } - // optional whitespace and then an expected character @inline def char(trace: List[JsonError], in: OneCharReader, c: Char): Unit = { val got = in.nextNonWhitespace() if (got != c) error(s"'$c'", got, trace) } - @inline def charOnly( - trace: List[JsonError], - in: OneCharReader, - c: Char - ): Unit = { + @inline def charOnly(trace: List[JsonError], in: OneCharReader, c: Char): Unit = { val got = in.readChar() if (got != c) error(s"'$c'", got, trace) } - // non-positional for performance @inline private[this] def isNumber(c: Char): Boolean = (c: @switch) match { case '+' | '-' | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '.' | 'e' | 'E' => true case _ => false } - def readChars( - trace: List[JsonError], - in: OneCharReader, - expect: Array[Char], - errMsg: String - ): Unit = { + def readChars(trace: List[JsonError], in: OneCharReader, expect: Array[Char], errMsg: String): Unit = { var i: Int = 0 while (i < expect.length) { if (in.readChar() != expect(i)) error(s"expected '$errMsg'", trace) diff --git a/zio-json/shared/src/test/scala-2.13/zio/json/CodecVersionSpecificSpec.scala b/zio-json/shared/src/test/scala-2.13/zio/json/CodecVersionSpecificSpec.scala new file mode 100644 index 000000000..155d86754 --- /dev/null +++ b/zio-json/shared/src/test/scala-2.13/zio/json/CodecVersionSpecificSpec.scala @@ -0,0 +1,18 @@ +package zio.json + +import zio.test.Assertion._ +import zio.test._ + +import scala.collection.immutable + +object CodecVersionSpecificSpec extends ZIOSpecDefault { + val spec: Spec[Environment, Any] = + suite("CodecSpec")( + test("ArraySeq") { + val jsonStr = """["5XL","2XL","XL"]""" + val expected = immutable.ArraySeq("5XL", "2XL", "XL") + + assert(jsonStr.fromJson[immutable.ArraySeq[String]])(isRight(equalTo(expected))) + } + ) +} diff --git a/zio-json/shared/src/test/scala-2.13/zio/json/DecoderVersionSpecificSpec.scala b/zio-json/shared/src/test/scala-2.13/zio/json/DecoderVersionSpecificSpec.scala new file mode 100644 index 000000000..70fb34146 --- /dev/null +++ b/zio-json/shared/src/test/scala-2.13/zio/json/DecoderVersionSpecificSpec.scala @@ -0,0 +1,30 @@ +package zio.json + +import zio.json.ast.Json +import zio.test.Assertion._ +import zio.test._ + +import scala.collection.immutable + +object DecoderVersionSpecificSpec extends ZIOSpecDefault { + + val spec: Spec[Environment, Any] = + suite("Decoder")( + suite("fromJson")( + test("ArraySeq") { + val jsonStr = """["5XL","2XL","XL"]""" + val expected = immutable.ArraySeq("5XL", "2XL", "XL") + + assert(jsonStr.fromJson[immutable.ArraySeq[String]])(isRight(equalTo(expected))) + } + ), + suite("fromJsonAST")( + test("ArraySeq") { + val json = Json.Arr(Json.Str("5XL"), Json.Str("2XL"), Json.Str("XL")) + val expected = immutable.ArraySeq("5XL", "2XL", "XL") + + assert(json.as[Seq[String]])(isRight(equalTo(expected))) + } + ) + ) +} diff --git a/zio-json/shared/src/test/scala-2.13/zio/json/EncoderVesionSpecificSpec.scala b/zio-json/shared/src/test/scala-2.13/zio/json/EncoderVesionSpecificSpec.scala new file mode 100644 index 000000000..b8c4ac448 --- /dev/null +++ b/zio-json/shared/src/test/scala-2.13/zio/json/EncoderVesionSpecificSpec.scala @@ -0,0 +1,31 @@ +package zio.json + +import zio.json.ast.Json +import zio.test.Assertion._ +import zio.test._ + +import scala.collection.immutable + +object EncoderVesionSpecificSpec extends ZIOSpecDefault { + + val spec: Spec[Environment, Any] = + suite("Encoder")( + suite("toJson")( + test("collections") { + assert(immutable.ArraySeq[Int]().toJson)(equalTo("[]")) && + assert(immutable.ArraySeq(1, 2, 3).toJson)(equalTo("[1,2,3]")) && + assert(immutable.ArraySeq[String]().toJsonPretty)(equalTo("[]")) && + assert(immutable.ArraySeq("foo", "bar").toJsonPretty)(equalTo("[\n \"foo\",\n \"bar\"\n]")) + } + ), + suite("toJsonAST")( + test("collections") { + val arrEmpty = Json.Arr() + val arr123 = Json.Arr(Json.Num(1), Json.Num(2), Json.Num(3)) + + assert(immutable.ArraySeq[Int]().toJsonAST)(isRight(equalTo(arrEmpty))) && + assert(immutable.ArraySeq(1, 2, 3).toJsonAST)(isRight(equalTo(arr123))) + } + ) + ) +} diff --git a/zio-json/shared/src/test/scala-3/zio/json/CodecVersionSpecificSpec.scala b/zio-json/shared/src/test/scala-3/zio/json/CodecVersionSpecificSpec.scala new file mode 100644 index 000000000..155d86754 --- /dev/null +++ b/zio-json/shared/src/test/scala-3/zio/json/CodecVersionSpecificSpec.scala @@ -0,0 +1,18 @@ +package zio.json + +import zio.test.Assertion._ +import zio.test._ + +import scala.collection.immutable + +object CodecVersionSpecificSpec extends ZIOSpecDefault { + val spec: Spec[Environment, Any] = + suite("CodecSpec")( + test("ArraySeq") { + val jsonStr = """["5XL","2XL","XL"]""" + val expected = immutable.ArraySeq("5XL", "2XL", "XL") + + assert(jsonStr.fromJson[immutable.ArraySeq[String]])(isRight(equalTo(expected))) + } + ) +} diff --git a/zio-json/shared/src/test/scala-3/zio/json/DecoderVersionSpecificSpec.scala b/zio-json/shared/src/test/scala-3/zio/json/DecoderVersionSpecificSpec.scala new file mode 100644 index 000000000..70fb34146 --- /dev/null +++ b/zio-json/shared/src/test/scala-3/zio/json/DecoderVersionSpecificSpec.scala @@ -0,0 +1,30 @@ +package zio.json + +import zio.json.ast.Json +import zio.test.Assertion._ +import zio.test._ + +import scala.collection.immutable + +object DecoderVersionSpecificSpec extends ZIOSpecDefault { + + val spec: Spec[Environment, Any] = + suite("Decoder")( + suite("fromJson")( + test("ArraySeq") { + val jsonStr = """["5XL","2XL","XL"]""" + val expected = immutable.ArraySeq("5XL", "2XL", "XL") + + assert(jsonStr.fromJson[immutable.ArraySeq[String]])(isRight(equalTo(expected))) + } + ), + suite("fromJsonAST")( + test("ArraySeq") { + val json = Json.Arr(Json.Str("5XL"), Json.Str("2XL"), Json.Str("XL")) + val expected = immutable.ArraySeq("5XL", "2XL", "XL") + + assert(json.as[Seq[String]])(isRight(equalTo(expected))) + } + ) + ) +} diff --git a/zio-json/shared/src/test/scala-3/zio/json/EncoderVesionSpecificSpec.scala b/zio-json/shared/src/test/scala-3/zio/json/EncoderVesionSpecificSpec.scala new file mode 100644 index 000000000..b8c4ac448 --- /dev/null +++ b/zio-json/shared/src/test/scala-3/zio/json/EncoderVesionSpecificSpec.scala @@ -0,0 +1,31 @@ +package zio.json + +import zio.json.ast.Json +import zio.test.Assertion._ +import zio.test._ + +import scala.collection.immutable + +object EncoderVesionSpecificSpec extends ZIOSpecDefault { + + val spec: Spec[Environment, Any] = + suite("Encoder")( + suite("toJson")( + test("collections") { + assert(immutable.ArraySeq[Int]().toJson)(equalTo("[]")) && + assert(immutable.ArraySeq(1, 2, 3).toJson)(equalTo("[1,2,3]")) && + assert(immutable.ArraySeq[String]().toJsonPretty)(equalTo("[]")) && + assert(immutable.ArraySeq("foo", "bar").toJsonPretty)(equalTo("[\n \"foo\",\n \"bar\"\n]")) + } + ), + suite("toJsonAST")( + test("collections") { + val arrEmpty = Json.Arr() + val arr123 = Json.Arr(Json.Num(1), Json.Num(2), Json.Num(3)) + + assert(immutable.ArraySeq[Int]().toJsonAST)(isRight(equalTo(arrEmpty))) && + assert(immutable.ArraySeq(1, 2, 3).toJsonAST)(isRight(equalTo(arr123))) + } + ) + ) +}