I am receiving a json object like this:
{"key": "value", "embedded": "{\"foo\": \"bar\"}"}
I can derive a decoder with circe like this:
case class Data(key: String, embedded: String)
implicit val dataDecoder: Decoder[Data] = deriveDecoder[Data]
But I would like to handle it like this:
case class JsonString[A](value: A)
case class FooBar(foo: String)
case class Data(key: String, embedded: JsonString[FooBar])
implicit def jsonStringDecoder[A](implicit d: Decoder[A]): Decoder[JsonString[A]] = ???
implicit val fooBarDecoder: Decoder[FooBar] = deriveDecoder[FooBar]
implicit val dataDecoder: Decoder[Data] = deriveDecoder[Data]
However I can't get the types to line up for jsonStringDecoder
. The closest I can get is:
import cats.syntax.either._
implicit def jsonStringDecoder[A](implicit decoder: Decoder[A]): Decoder[JsonString[A]] =
Decoder.decodeString.emap { text =>
for {
json <- parser.parse(text).leftMap(_.getMessage)
value <- decoder.decodeJson(json).leftMap(_.getMessage)
} yield JsonString(value)
}
But the constant usage of .leftMap(_.getMessage)
feels inappropriate. But I don't know a better way to convert a ParsingFailure
to a DecodingFailure
.
If the only your issue is that you take .leftMap(_.getMessage)
multiple times then you can work with Either[Error, A]
instead of Either[String, A]
(io.circe.Error
is a parent of ParsingFailure
and DecodingFailure
) and take .leftMap(_.getMessage)
once at the end:
implicit def jsonStringDecoder[A: Decoder]: Decoder[JsonString[A]] =
Decoder.decodeString.emap(text => (for {
json <- parse(text)
value <- json.as[A]
} yield JsonString(value)).leftMap(_.getMessage))
Or with @LuisMiguelMejíaSuárez's idea to transform ParsingFailure
into DecodingFailure
via DecodingFailure.fromThrowable(..)
you can work in terms of cursors:
implicit def jsonStringDecoder[A: Decoder]: Decoder[JsonString[A]] =
(c: HCursor) => for {
text <- c.as[String] //Decoder.decodeString(c)
json <- parse(text).leftMap(parsingFailure =>
DecodingFailure.fromThrowable(parsingFailure, List())
)
value <- json.as[A]
} yield JsonString(value)
One more option is .emapTry
implicit def jsonStringDecoder[A: Decoder]: Decoder[JsonString[A]] =
Decoder.decodeString.emapTry(text => (for {
json <- parse(text).leftMap(parsingFailure =>
DecodingFailure.fromThrowable(parsingFailure, List())
)
value <- json.as[A]
} yield JsonString(value)).toTry)
or even simpler (thanks to @LuisMiguelMejíaSuárez again)
implicit def jsonStringDecoder[A: Decoder]: Decoder[JsonString[A]] =
Decoder.decodeString.emapTry(text =>
parser.decode[A](text).map(JsonString.apply).toTry
)