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 d597b1c3..3685acdb 100644 --- a/zio-json/shared/src/main/scala/zio/json/JsonDecoder.scala +++ b/zio-json/shared/src/main/scala/zio/json/JsonDecoder.scala @@ -201,6 +201,37 @@ trait JsonDecoder[A] extends JsonDecoderPlatformSpecific[A] { final def zipWith[B, C](that: => JsonDecoder[B])(f: (A, B) => C): JsonDecoder[C] = self.zip(that).map(f.tupled) + /** + * Returns a new codec that combines this codec and the specified codec into a single codec that + * decodes the input into a tuple of the values by the respective codecs. + */ + final def newZip[B](that: => JsonDecoder[B]): JsonDecoder[(A, B)] = new JsonDecoder[(A, B)] { + def unsafeDecode(trace: List[JsonError], in: RetractReader): (A, B) = { + val in2 = new zio.json.internal.WithRecordingReader(in, 64) + val a = self.unsafeDecode(trace, in2) + in2.rewind() + val b = that.unsafeDecode(trace, in2) + (a, b) + } + } + + /** + * NewZips two codecs, but discards the output on the right hand side. + */ + final def newZipLeft[B](that: => JsonDecoder[B]): JsonDecoder[A] = self.newZipWith(that)((a, _) => a) + + /** + * NewZips two codecs, but discards the output on the left hand side. + */ + final def newZipRight[B](that: => JsonDecoder[B]): JsonDecoder[B] = self.newZipWith(that)((_, b) => b) + + /** + * NewZips two codecs into one, transforming the outputs of zip codecs by the specified function. + */ + final def newZipWith[B, C](that: => JsonDecoder[B])(f: (A, B) => C): JsonDecoder[C] = + self.newZip(that).map(f.tupled) + + def unsafeDecodeMissing(trace: List[JsonError]): A = throw JsonDecoder.UnsafeJson(JsonError.Message("missing") :: trace) diff --git a/zio-json/shared/src/test/scala/zio/json/DecoderSpec.scala b/zio-json/shared/src/test/scala/zio/json/DecoderSpec.scala index ee8b765e..716e4fa4 100644 --- a/zio-json/shared/src/test/scala/zio/json/DecoderSpec.scala +++ b/zio-json/shared/src/test/scala/zio/json/DecoderSpec.scala @@ -294,6 +294,17 @@ object DecoderSpec extends ZIOSpecDefault { ) ) ) + }, + test("newZip") { + final case class Foo(a: Int) + final case class Bar(b: String) + + implicit val fooDecoder: JsonDecoder[Foo] = DeriveJsonDecoder.gen[Foo] + implicit val barDecoder: JsonDecoder[Bar] = DeriveJsonDecoder.gen[Bar] + implicit val fooAndBarDecoder: JsonDecoder[(Foo, Bar)] = JsonDecoder[Foo].newZip(JsonDecoder[Bar]) + + val json = """{"a": 1, "b": "2"}""" + assert(json.fromJson[(Foo, Bar)])(isRight(equalTo((Foo(1), Bar("2"))))) } ), suite("fromJsonAST")(