Search code examples
jsonscalacirce

Errors Unmarshalling JSON array to List of case classesc


I have been trying to unmarshal JSON from an external service into a list of case classes using circe (I am a beginner to both circe and Scala).

The case class is as follows:

case class User(
  id:            Int,
  username:      Option[String],
  permalink_url: Option[String],
  avatar_url:    Option[String],
  tracks:        Option[List[Track]],
  favorites:     Option[List[Track]],
  followings:    Option[List[User]] // The JSON below would populate this field
)

I have so far used automatic derivation for all other types, but this one uses the structure

   {
      "collection": [
          {
            "id": 42,
            "username": "user",
            "permalink_url": "foo://bar.baz",
            "followers_count": 15089,
            "followings_count": 498,
            "reposts_count": 31,
            "comments_count": 13,
            "online": false,
            "likes_count": 0,
            "playlist_count": 10
        },
        ...etc, etc
      ]
    }

To handle this, I have implemented a custom decoder:

implicit val followingsDecoder: Decoder[List[User]] = Decoder.instance( c =>
    for {
      collection <- c.downField("collection").as[List[User]]
    } yield collection
)

This fails and yields the following error:

DecodingFailure(CNil, List(DownField(collection)))

I have also tried using circe's automatic decoder derivation for this, which yields a different error:

DecodingFailure(CanBuildFrom for A, List())

I understand that circe's error messaging does not provide the field on which decoding failed. I do not need any of the fields not specified in the User case class, and am unsure whether this is an issue with the JSON I am receiving, or with the way that I am trying to decode it.

Is there something here that I am missing? I have tried other means of decoding such as semiautomatic derivation as well as attempting to unmarshal the list into a case class of its own.

  implicit val followingsDecoder: Decoder[List[User]] = deriveDecoder[List[User]].prepare(
      _.downField("collection")
  )

Is there something I've missed here? How can I parse this JSON into a list of case classes?


Solution

  • The case class definition for User is valid, but the use of implicit followingsDecoder: Decoder is not mandatory to unmarshall this. collection can also be wrapped into a case class and the decoding will be done automatically. So, if we have

    case class UsersCollection(collection: List[User])
    
    // I also added this one to make it compile:
    case class Track(id: Int)
    

    And the original json, with added followings field:

    private val json =
      """
        |{
        |      "collection": [
        |          {
        |            "id": 42,
        |            "username": "user",
        |            "permalink_url": "foo://bar.baz",
        |            "followers_count": 15089,
        |            "followings_count": 498,
        |            "reposts_count": 31,
        |            "comments_count": 13,
        |            "online": false,
        |            "likes_count": 0,
        |            "playlist_count": 10,
        |            "followings": [
        |             { "id": 43 },
        |             { "id": 44 },
        |             { "id": 45 }
        |            ]
        |        }
        |      ]
        |    }
      """.stripMargin
    

    It parses properly:

    val decoded = decode[UsersCollection](json)
    println(decoded)
    
    // Right(UsersCollection(List(
    //  User(42,Some(user),Some(foo://bar.baz),None,None,None,
    //       Some(List(User(43,None,None,None,None,None,None),
    //                 User(44,None,None,None,None,None,None),
    //                 User(45,None,None,None,None,None,None)))))))
    

    So if it is possible in your case, I'd suggest adding another ad-hoc case class wrapping the list of users instead of using a custom decoder.