Your Location is: Home > Scala

How can i make marshaller for tree-like case class?

From: Reykjavik View: 3034 Dmitry Reutov 

Question

I want to make marshaller for case class which has fields referred to the same class.

case class TreeNode (name: String, parentNode: Option[TreeNode])

if i make serializer

implicit val nodeJson = jsonFormat2(TreeNode)

i get an error that no imlicits found for parameter Option[TreeNode] is there are any way to solve such problem except writing serialization from scratch?

PS Some more attempts i made with encoder

private def toNode (node: TreeNode): Map[String, Any] = {
    val parent = node.parentNode.map(toNode).orNull
    Map[String, Any] ("name" -> node.name, "parentNode" -> parent)
  }

implicit val treeNodeEncoder: Encoder[TreeNode] =
  Encoder.forProduct2("name", "parentNode")(n =>
    (n.name, n.parentNode.map(toNode).orNull)
  )

it does not work as well, because Map[String, Any] has no implicit either

Best answer

circe supports this out of the box, you just need to use the semiautomatic / automatic derivation mechanism provided by the library.
You can also modify the printer you want to use to control the identation and if you want to include or not nulls

Take a look to this code:

import io.circe.{Decoder, Encoder, Printer, parser}
import io.circe.syntax._
import io.circe.generic.semiauto._

final case class TreeNode(name: String, parentNode: Option[TreeNode] = None)
object TreeNode {
  implicit final val decoder: Decoder[TreeNode] = deriveDecoder
  implicit final val encoder: Encoder[TreeNode] = deriveEncoder
}

val data = TreeNode(name = "child", parentNode = Some(TreeNode(name = "parent")))

val printer = Printer.spaces2SortKeys.copy(dropNullValues = true)
val json = printer.print(data.asJson)
println(json)

println("------------------")

val result = parser.decode[TreeNode](json)
println(result)

You can see it running here

Another answer

Assuming you're trying to make this work with circe, here is a working example:


import io.circe.{Encoder,Decoder}
import io.circe.generic.semiauto.{deriveEncoder,deriveDecoder}
import io.circe.syntax._

final case class TreeNode (name: String, parentNode: Option[TreeNode])

object TreeNode {
  implicit val encoder: Encoder[TreeNode] = deriveEncoder
  implicit val decoder: Decoder[TreeNode] = deriveDecoder
}

val a = TreeNode("a", None)
val b = TreeNode("b", Some(a))
val c = TreeNode("c", Some(b))

println(c.asJson)

Output

{
  "name" : "c",
  "parentNode" : {
    "name" : "b",
    "parentNode" : {
      "name" : "a",
      "parentNode" : null
    }
  }
}

Also note that representing trees starting from the leaves rather than the roots is unusual.

Another answer

Thanks everyone for help, just to make it complete one more way i discovered, how to make it manually if necesserily

implicit val treeNodeEncoder: Encoder[TreeNode] = new Encoder[TreeNode] {
    def apply(a: TreeNode): Json = {
      val list = List(
        ("name" -> Json.fromString(a.name)),
        ("parentNode" -> a.parentNode.map(this.apply).orNull),
      ).filterNot(_._2 == null)

      Json.obj(list: _*)
    }
  }

This way we can avoid to write parentNode as null and just skip it if it is None