diff --git a/zio-json/shared/src/main/scala/zio/json/encoder.scala b/zio-json/shared/src/main/scala/zio/json/encoder.scala index c78132d92..a611822bc 100644 --- a/zio-json/shared/src/main/scala/zio/json/encoder.scala +++ b/zio-json/shared/src/main/scala/zio/json/encoder.scala @@ -106,6 +106,22 @@ object JsonEncoder extends GeneratedTupleEncoders with EncoderLowPriority0 { } + implicit val char: JsonEncoder[Char] = new JsonEncoder[Char] { + + override def unsafeEncode(a: Char, indent: Option[Int], out: Write): Unit = { + out.write('"') + a match { + case '"' => out.write("\\\"") + case '\\' => out.write("\\\\") + case c => + if (c < ' ') out.write("\\u%04x".format(c.toInt)) + else out.write(c.toString) + } + out.write('"') + } + + } + private[json] def explicit[A](f: A => String): JsonEncoder[A] = new JsonEncoder[A] { def unsafeEncode(a: A, indent: Option[Int], out: Write): Unit = out.write(f(a)) } @@ -115,7 +131,6 @@ object JsonEncoder extends GeneratedTupleEncoders with EncoderLowPriority0 { } implicit val boolean: JsonEncoder[Boolean] = explicit(_.toString) - implicit val char: JsonEncoder[Char] = string.contramap(_.toString) implicit val symbol: JsonEncoder[Symbol] = string.contramap(_.name) implicit val byte: JsonEncoder[Byte] = explicit(_.toString) implicit val short: JsonEncoder[Short] = explicit(_.toString) diff --git a/zio-json/shared/src/test/scala/zio/json/CodecSpec.scala b/zio-json/shared/src/test/scala/zio/json/CodecSpec.scala index 7732baa75..31ea1e56a 100644 --- a/zio-json/shared/src/test/scala/zio/json/CodecSpec.scala +++ b/zio-json/shared/src/test/scala/zio/json/CodecSpec.scala @@ -111,9 +111,39 @@ object CodecSpec extends DefaultRunnableSpec { assert(jsonStr.fromJson[Chunk[String]])(isRight(equalTo(expected))) } + ), + suite("Encode -> Decode")( + suite("control chars")( + test("tab") { + assert(encodeDecode(JsonCodec.char, '\t'))(isRight(equalTo('\t'))) + }, + test("carriage return") { + assert(encodeDecode(JsonCodec.char, '\r'))(isRight(equalTo('\r'))) + }, + test("newline") { + assert(encodeDecode(JsonCodec.char, '\n'))(isRight(equalTo('\n'))) + }, + test("form feed") { + assert(encodeDecode(JsonCodec.char, '\f'))(isRight(equalTo('\f'))) + }, + test("backspace") { + assert(encodeDecode(JsonCodec.char, '\b'))(isRight(equalTo('\b'))) + }, + test("escape") { + assert(encodeDecode(JsonCodec.char, '\\'))(isRight(equalTo('\\'))) + }, + test("quote") { + assert(encodeDecode(JsonCodec.char, '"'))(isRight(equalTo('"'))) + } + ) ) ) + private def encodeDecode[A](codec: JsonCodec[A], value: A): Either[String, A] = + codec.decodeJson( + codec.encodeJson(value, None) + ) + object exampleproducts { case class Parameterless()