Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Encoding nested classes using scala argonaut

Tags:

scala

argonaut

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?

like image 880
Daniel Avatar asked Mar 27 '14 00:03

Daniel


2 Answers

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.

like image 127
Travis Brown Avatar answered Sep 30 '22 06:09

Travis Brown


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()))
like image 21
gerferra Avatar answered Sep 30 '22 07:09

gerferra