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?
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.