3

I am trying to use Circe's custom codecs to decode json to a List of a trait Feature which is broken down into two case classes:

trait Feature {
  def name: String
  def index: Int
  def frequency: Int
}

case class NumericFeature(name, index, frequency) extends Feature
case class SetFeature(name, index, set, frequency) extends Feature

from the json shown here:

[
 {
   "name" : "ElectionReturns_G16CountyTurnoutAllRegisteredVoters",
   "index" : 1770,
   "frequency" : 2992
 },
{
   "name" : "CommercialDataLL_Home_Owner_Or_Renter",
   "index" : 1112,
   "set" : [
     "Likely Homeowner",
     "",
     "Likely Renter",
    ],
   "frequency" : 2537
},
   .
   .
   .
   .
]

This is the code that works for a single instance of a case class aka:

{
   "name" : "ElectionReturns_G16CountyTurnoutAllRegisteredVoters",
   "index" : 1770,
   "frequency" : 2992
 }
import io.circe.Json
import io.circe._
import io.circe.parser._

implicit val decoder: Decoder[Feature] = new Decoder[Feature] {
      final def apply(h: HCursor): Decoder.Result[Feature] = for {

          name <- h.get[String]("name")
          index <- h.get[Int]("index")
          set <- h.getOrElse[Set[String]]("set")(Set[String]())
          frequency <- h.get[Int]("frequency")
          feature: Feature = set match {
            case s if s.isEmpty => new NumericFeature(name, index, frequency)
            case _ => new SetFeature(name, index, set, frequency)
          }
        } yield feature
     }

val decoded = decoder.decodeJson(parse(json).getOrElse(Json.Null))

How can I adapt this to output a list? I have tried just changing the output type of the decoder to List[Feature] because I thought the for loop would yield a list, but this isn't the case.

Here is my attempt at a Decoder returning a Decoder[List[Feature]]:

implicit val decoder: Decoder[List[Feature]] = new Decoder[List[Feature]] {
      final def apply(h: HCursor): Decoder.Result[List[Feature]]= {

        val feature = for {

          name <- h.get[String]("name")
          index <- h.get[Int]("index")
          set <- h.getOrElse[Set[String]]("set")(Set[String]())
          frequency <- h.get[Int]("frequency")
          feature: Feature = set match {
            case s if s.isEmpty => new NumericFeature(name, index, frequency)
            case _ => new SetFeature(name, index, set, frequency)
          }
        } yield feature
        feature.asInstanceOf[Decoder.Result[List[Feature]]]
      }
    }
val decoded = decoder.decodeJson(parse(json).getOrElse(Json.Null))

  • So you don't have the wrapping `[` and `]` around the JSON objects? – Joan Jul 04 '19 at 01:08
  • If you have a `Decoder[Feature]`, circe will automatically provide a `Decoder[List[Feature]]`—this is generally the way type classes work in Scala (and other languages). – Travis Brown Jul 04 '19 at 11:51
  • @Travis Brown - This doesn't seem to be the case when I have been testing, perhaps the way I am using it is wrong? I am using `decoder.decodeJson(parse(json).getOrElse(Json.Null))` to decode my json. – Riley White Jul 08 '19 at 15:09
  • @RileyWhite I'll try to take a look if you provide a complete example. – Travis Brown Jul 08 '19 at 15:13
  • @TravisBrown Sure, what else do you need exactly, that is almost all the code I am using, thanks. – Riley White Jul 08 '19 at 15:31
  • I can't copy and paste what you have now, since there are missing definitions, and you don't actually show anywhere you're trying to decode a `List[Feature]`. – Travis Brown Jul 08 '19 at 15:37
  • Ok, I have updated my answer, which includes where I try to use the decoder now. – Riley White Jul 08 '19 at 15:49
  • @RileyWhite You shouldn't ever need to write a `Decoder[List[X]]`—you provide an implicit `Decoder[X]`, and then use something like `io.circe.parser.decode[List[X]](myJson)`. – Travis Brown Jul 08 '19 at 20:39
  • Hi @RileyWhite. I had a similar challenge as you and I _think_ I finally figured it out. I have created a gist that tries to show how I went about it. https://gist.github.com/dholtz/5065d5ea77f345589ad607abceb0ff75 The thing that I missed were the docs around encoding/decoding ADT's https://circe.github.io/circe/codecs/adt.html – dholtz Dec 15 '19 at 20:55

2 Answers2

0

Just use io.circe.parser.decode given you have the Decoder[Feature] in scope:

io.circe.parser.decode[Seq[Feature]](json)

You don't have to provide a Decoder[Seq[Feature]].

Joan
  • 4,079
  • 2
  • 28
  • 37
  • Sorry I actually do have the wrapping `[]` in my JSON, I have updated my post. Also, I can't use the decode function because I need to handle two different cases. – Riley White Jul 08 '19 at 15:14
  • I edited my answer, that should just work. Make sure not to define any `Decoder[Seq[Feature]]`, they are provided by circe. – Joan Jul 09 '19 at 02:04
-1

try to use custom decoder. This approach works for me:

import cats.Show
import cats.implicits._
import io.circe.Decoder

object Feature {
  implicit val featureDecoder: Decoder[Feature] = List[Decoder[Feature]](
    Decoder[NumericFeature].widen,
    Decoder[SetFeature].widen
  ).reduceLeft(_ or _)
}
Roman Kazanovskyi
  • 3,370
  • 1
  • 21
  • 22