jsonscalacirce

How can I decode a string embeded in a json field as json with circe?


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.


Solution

  • 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
      )