Search code examples
jsonscalacircescala-3dotty

circe doesn't see field when it contains an array


I've got 2 "tests", of which the one where I'm trying to decode a user works, but the one where I'm trying to decode a list of users doesn't:

import User._
import io.circe._
import io.circe.syntax._
import io.circe.parser.decode

class UserSuite extends munit.FunSuite:
  test("List of users can be decoded") {

    val json = """|{
                  |   "data" : [
                  |       {
                  |         "id" : "someId",
                  |         "name" : "someName",
                  |         "username" : "someusername"
                  |       },
                  |       {
                  |         "id" : "someId",
                  |         "name" : "someName",
                  |         "username" : "someusername"
                  |       }
                  |   ]
                  |}""".stripMargin    
    println(decode[List[User]](json))
  }

  test("user can be decoded") {
    val json = """|{
                  |   "data" : {
                  |         "id" : "someId",
                  |         "name" : "someName",
                  |         "username" : "someusername"
                  |       }
                  |}""".stripMargin
    println(decode[User](json))
  }

The failing one produces

Left(DecodingFailure(List, List(DownField(data))))

despite the fact that both the json's relevant structure and the decoders (below) are the same.

final case class User(
    id: String,
    name: String,
    username: String
)

object User:
    given Decoder[List[User]] = 
        deriveDecoder[List[User]].prepare(_.downField("data"))
    
    given Decoder[User] = 
        deriveDecoder[User].prepare(_.downField("data"))

As far as I understand this should work, even according to one of Travis' older replies but it doesn't.

Is this a bug? Am I doing something wrong?

For reference, This is Scala 3.2.0 and circe 0.14.1


Solution

  • The thing is that that you need two different encoders for User, the one expecting data field to decode the 2nd json and the one not expecting data field while deriving decoder for a list. Otherwise the 1st json should be

    """|{
       |   "data" : [
       |       {
       |          "data" : 
       |              {
       |                 "id" : "someId",
       |                 "name" : "someName",
       |                 "username" : "someusername"
       |              }
       |       },
       |       {
       |          "data" : 
       |              {
       |                 "id" : "someId",
       |                 "name" : "someName",
       |                 "username" : "someusername"
       |              }
       |       }
       |   ]
       |}""
    

    It's better to be explicit now

    final case class User(
                           id: String,
                           name: String,
                           username: String
                         )
    
    object User {
      val userDec: Decoder[User] = semiauto.deriveDecoder[User]
      val preparedUserDec: Decoder[User] = userDec.prepare(_.downField("data"))
    
      val userListDec: Decoder[List[User]] = {
        implicit val dec: Decoder[User] = userDec
        Decoder[List[User]].prepare(_.downField("data"))
      }
    }
    
    val json =
      """|{
         |   "data" : [
         |       {
         |         "id" : "someId",
         |         "name" : "someName",
         |         "username" : "someusername"
         |       },
         |       {
         |         "id" : "someId",
         |         "name" : "someName",
         |         "username" : "someusername"
         |       }
         |   ]
         |}""".stripMargin
    
    decode[List[User]](json)(User.userListDec)
    // Right(List(User(someId,someName,someusername), User(someId,someName,someusername)))
    
    
    val json1 =
        """|{
           |   "data" : {
           |         "id" : "someId",
           |         "name" : "someName",
           |         "username" : "someusername"
           |       }
           |}""".stripMargin
    
    decode[User](json1)(User.preparedUserDec)
    // Right(User(someId,someName,someusername))