Home > Blockchain >  Decode List[String] to List[JSONObject(key,value)] in circe scala
Decode List[String] to List[JSONObject(key,value)] in circe scala

Time:04-10

Given incoming json like below, how can i decode it the given case class based on condition.

Incoming JSON

{
    "config": {
        "files": ["welcome"],
        "channel": "media"
    }
}

Case Classes

case class File(`type`: String, value: String)

case class Config(files: List[File],
                  channel: String = "BC")

object Config{
  implicit val FileDecoder: Decoder[File] = deriveDecoder[File]
  implicit val ConfigDecoder: Decoder[Config] = deriveDecoder[Config]
}

case class Inventory(config: Config)

object Inventory {
  implicit val InventoryDecoder: Decoder[Inventory] = deriveDecoder[Inventory]
}

I do not have control on incoming files values in json it can be List[String] or List[File] so i need to handle both cases in my decoding logic.

So as we can see above my aim is to check if the incomes files values is List[String] then transform that values to below where type is hardcoded to "audio"

"files": [{
            "type": "audio",
            "value": "welcome.mp3"
        }],

The overall json should look like below before it mapped into case classes for auto decoding.

{
    "config": {
        "files": [{
            "type": "audio",
            "value": "welcome.mp3"
        }],
        "channel": "media"
    }
}

what i understood that this can be achieved either by transforming the json before decoding or can also be achieved during the decoding of files.

I tried writing the decoding logic at File level but i am not able to succeed. I am not getting the crux of how to do this.

Tried code

  implicit val FileDecoder: Decoder[File] = deriveDecoder[File].prepare { (aCursor: ACursor) =>
  {

    if(!aCursor.values.contains("type")){
      aCursor.values.map( v =>
        Json.arr(
          Json.fromFields(
            Seq(
              ("type", Json.fromString("audio")),
              ("value", v.head)
            )
          )
        )
      )
    }
  }
  }

CodePudding user response:

We can use a custom Decoder for File to provide a default value to type

final case class File(`type`: String, value: String)
object File {
  implicit final val FileDecoder: Decoder[File] =
    Decoder.instance { cursor =>
      (
        cursor.getOrElse[String](k = "type")(fallback = "audio"),
        cursor.get[String](k = "value")
      ).mapN(File.apply)
    }.or(
      Decoder[String].map(value => File(`type` = "audio", value))
    )
}

Which can be used like this:

val data =
"""[
  {
    "type": "audio",
    "value": "welcome.mp3"
  },
  {
    "value": "foo.mp3"
  },
  "bar.mp3"
]"""

parser.decode[List[File]](data)
// res: Either[io.circe.Error, List[File]] =
//  Right(List(
//    File("audio", "welcome.mp3"),
//    File("audio", "foo.mp3"),
//    File("audio", "bar.mp3")
//  ))

You can see the code running here.

  • Related