Home > Enterprise >  circe doesn't see field when it contains an array
circe doesn't see field when it contains an array

Time:11-20

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

CodePudding user response:

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))
  • Related