Encoding nested classes using scala argonaut
Asked Answered
L

2

9

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?

Literally answered 27/3, 2014 at 0:10 Comment(0)
V
6

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.

Vining answered 27/3, 2014 at 0:42 Comment(2)
Thanks Travis! It's compiling now, but still not working. I'm facing a stackoverflow, when trying to decode a nested structure. I updated my question accordingly.Literally
Oh, right. Play's JSON library provides a lazyRead method for this kind of situation, but I'm not sure there's anything comparable in Argonaut. Might be worth a question on the mailing list or GitHub.Vining
B
4

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()))
Boudoir answered 29/5, 2014 at 12:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.