Because, this is not very typical case, circe does not provide such behavior out of the box, so you will need implement own Encoder and Decoder. Luckily, it is fairly easy to do with this lib.
Please, see code example below:
import io.circe._
import io.circe.syntax._
import io.circe.generic.auto._
val separator = ","
// Implement own `Encoder` to render array of string as JSON string, separated with comma inside
implicit val encoder: Encoder[Array[String]] = Encoder[String].contramap(_.mkString(separator))
// Implement own `Decoder` to parse JSON string as array
implicit val decoder: Decoder[Array[String]] = Decoder[String].map(_.split(separator))
// Class added for sake of example, because I guess you will use string array as a part of another structure
case class Foo(a: Array[String]) {
// override toString, because array toString works as default Object toString, which is not really readable
// Made for example readability, you don't need to do in your code
override def toString: String = s"Foo(a: ${a.mkString(", ")})"
}
val foo = Foo(Array("a1", "a2"))
val json = foo.asJson
println(json.noSpaces)
println(json.as[Foo])
Which produces next result:
{"a":"a1,a2"}
Right(Foo(a: a1,a2))
Hope this helps!