Encoding nested classes using scala argonaut

2019-04-21 10:49发布

问题:

I'm trying to encode/decode following case class

case class Person(name: String, age: Int, childs: List[Person])

using the following code:

object Person {
    implicit def PersonCodecJson =
        casecodec3(Person.apply, Person.unapply)("name", "age", "childs")

}

with argonaut, but I'm getting the following compiler error:

could not find implicit value for evidence parameter of type argonaut.EncodeJson[List[Person]]

Obviously, the compiler doesn't know how to handle encoding of List[Person], because it's used inside the definition of how to encode Person.

Is there a clever way to tell argonaut how to encode it the right way?

Update: Thanks to Travis: It's compiling now, but it's not working.

implicit def PersonCodecJson : CodecJson[Person] =
        casecodec3(Person.apply, Person.unapply)("name", "age", "childs")

leads to an infinite recursion and a stack overflow trying to decode

val input = """
    [{"name": "parent1", "age": 31, "childs": [{"name": "child1", "age": 2, "childs": []}]},
     {"name": "parent2", "age": 29, "childs": []}
    ]
    """
val persons = input.decodeOption[List[Person]].getOrElse(Nil)

results in

at Person$.PersonCodecJson(main.scala:8)
at Person$.PersonCodecJson(main.scala:8)
at Person$.PersonCodecJson(main.scala:8)
at Person$.PersonCodecJson(main.scala:8)
at Person$.PersonCodecJson(main.scala:8)
at Person$.PersonCodecJson(main.scala:8)
at Person$.PersonCodecJson(main.scala:8)
at Person$.PersonCodecJson(main.scala:8)
[debug]     Thread run-main-1 exited.
[debug] Interrupting remaining threads (should be all daemons).
[debug] Sandboxed run complete..
java.lang.RuntimeException: Nonzero exit code: 1
at scala.sys.package$.error(package.scala:27)
at sbt.BuildCommon$$anonfun$toError$1.apply(Defaults.scala:1653)
at sbt.BuildCommon$$anonfun$toError$1.apply(Defaults.scala:1653)
at scala.Option.foreach(Option.scala:236)
at sbt.BuildCommon$class.toError(Defaults.scala:1653)
at sbt.Defaults$.toError(Defaults.scala:35)
at sbt.Defaults$$anonfun$runTask$1$$anonfun$apply$36$$anonfun$apply$37.apply(Defaults.scala:656)
at sbt.Defaults$$anonfun$runTask$1$$anonfun$apply$36$$anonfun$apply$37.apply(Defaults.scala:654)
at scala.Function1$$anonfun$compose$1.apply(Function1.scala:47)
at sbt.$tilde$greater$$anonfun$$u2219$1.apply(TypeFunctions.scala:42)
at sbt.std.Transform$$anon$4.work(System.scala:64)

Is this approach to decode this nested json even valid? Do I have to tackle it completely different? Or is just another small piece of code missing?

回答1:

You're very close—you just need to specify the type explicitly:

object Person {
  implicit def PersonCodecJson: CodecJson[Person] =
    casecodec3(Person.apply, Person.unapply)("name", "age", "childs")
}

Just as Scala won't allow you to write a recursive method without an explicit result type, it won't find the implicit being defined inside the definition without one.

Not sure how clever that is, but it works.



回答2:

Apparently the problem is casecodec. If you create the decoder manually, it works:

implicit def PersonDecodeJson: DecodeJson[Person] =
    DecodeJson(c => for {
      name <- (c --\ "name").as[String]
      age <- (c --\ "age").as[Int]
      childs <- (c --\ "childs").as[List[Person]]
    } yield Person(name, age, childs)) 


val persons = input.decodeOption[List[Person]].getOrElse(Nil)
//> persons  : List[Person] = List(Person(parent1,31,List(Person(child1,2,List()))), Person(parent2,29,List()))