From ee114a6a1c9fc86f34d9d42473bc75c1ca460db2 Mon Sep 17 00:00:00 2001 From: amsavchuk Date: Mon, 13 May 2024 22:10:05 +0400 Subject: [PATCH] encodeJsonArrayPipeline produces incorrect JSON when stream is empty --- .../zio/json/JsonEncoderPlatformSpecific.scala | 17 ++++++++++------- .../zio/json/EncoderPlatformSpecificSpec.scala | 10 +++++++++- .../src/test/scala/zio/json/ast/JsonSpec.scala | 10 ++++++++++ 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/zio-json/jvm/src/main/scala/zio/json/JsonEncoderPlatformSpecific.scala b/zio-json/jvm/src/main/scala/zio/json/JsonEncoderPlatformSpecific.scala index 516fc41fd..3502c7270 100644 --- a/zio-json/jvm/src/main/scala/zio/json/JsonEncoderPlatformSpecific.scala +++ b/zio-json/jvm/src/main/scala/zio/json/JsonEncoderPlatformSpecific.scala @@ -43,17 +43,20 @@ trait JsonEncoderPlatformSpecific[A] { self: JsonEncoder[A] => ) } } - writeWriter <- ZIO.succeed(new WriteWriter(writer)) + writeWriter <- ZIO.succeed(new WriteWriter(writer)) + hasAtLeastOneElement <- Ref.make(false) push = { (is: Option[Chunk[A]]) => val pushChars = chunkBuffer.getAndUpdate(c => if (c.isEmpty) c else Chunk()) is match { case None => - ZIO.attemptBlocking(writer.close()) *> pushChars.map { terminal => - endWith.fold(terminal) { last => - // Chop off terminal delimiter - (if (delimiter.isDefined) terminal.dropRight(1) else terminal) :+ last - } + ZIO.attemptBlocking(writer.close()) *> pushChars.flatMap { terminal => + hasAtLeastOneElement.get.map(nonEmptyStream => + endWith.fold(terminal) { last => + // Chop off terminal delimiter if stream is not empty + (if (delimiter.isDefined && nonEmptyStream) terminal.dropRight(1) else terminal) :+ last + } + ) } case Some(xs) => @@ -64,7 +67,7 @@ trait JsonEncoderPlatformSpecific[A] { self: JsonEncoder[A] => for (s <- delimiter) writeWriter.write(s) } - } *> pushChars + } *> hasAtLeastOneElement.set(true).when(xs.nonEmpty) *> pushChars } } } yield push diff --git a/zio-json/jvm/src/test/scala-2/zio/json/EncoderPlatformSpecificSpec.scala b/zio-json/jvm/src/test/scala-2/zio/json/EncoderPlatformSpecificSpec.scala index ba37179d5..7479e378b 100644 --- a/zio-json/jvm/src/test/scala-2/zio/json/EncoderPlatformSpecificSpec.scala +++ b/zio-json/jvm/src/test/scala-2/zio/json/EncoderPlatformSpecificSpec.scala @@ -7,7 +7,7 @@ import testzio.json.data.googlemaps._ import testzio.json.data.twitter._ import zio.Chunk import zio.json.ast.Json -import zio.stream.ZStream +import zio.stream.{ ZSink, ZStream } import zio.test.Assertion._ import zio.test.{ ZIOSpecDefault, assert, _ } @@ -75,6 +75,14 @@ object EncoderPlatformSpecificSpec extends ZIOSpecDefault { } yield { assert(xs.mkString)(equalTo("""[{"id":1},{"id":2},{"id":3}]""")) } + }, + test("encodeJsonArrayPipeline, empty stream") { + val emptyArray = ZStream + .from(List()) + .via(JsonEncoder[String].encodeJsonArrayPipeline) + .run(ZSink.mkString) + + assertZIO(emptyArray)(equalTo("[]")) } ), suite("helpers in zio.json")( diff --git a/zio-json/shared/src/test/scala/zio/json/ast/JsonSpec.scala b/zio-json/shared/src/test/scala/zio/json/ast/JsonSpec.scala index 996a5b847..069f16594 100644 --- a/zio-json/shared/src/test/scala/zio/json/ast/JsonSpec.scala +++ b/zio-json/shared/src/test/scala/zio/json/ast/JsonSpec.scala @@ -267,6 +267,16 @@ object JsonSpec extends ZIOSpecDefault { assert(posts.get(combined))( isRight(equalTo(Json.Str("foo"))) ) + }, + test(">>>, identity") { + val obj = Json.Obj("a" -> Json.Num(1)) + + val fieldA = JsonCursor.field("a") + val identity = JsonCursor.identity + + val num = obj.get(fieldA >>> identity) + + assert(num)(isRight(equalTo(Json.Num(1)))) } ), suite("intersect")(