spray / spray-json

A lightweight, clean and simple JSON implementation in Scala
Apache License 2.0
969 stars 190 forks source link

recursive Json Does not seem to work as documented #338

Closed dr-jerry closed 2 years ago

dr-jerry commented 3 years ago

case class Foo(i: Int, foo: Foo)

trait PlayerJsonProtocol extends DefaultJsonProtocol {
  implicit val fooFormat: JsonFormat[Foo] = lazyFormat(jsonFormat(Foo, "i", "foo"))<-- error here "could not find implicit value for evidence parameter"
}

could not find implicit value for evidence parameter

scalaVersion := "2.12.8"

val akkaVersion = "2.5.20"
val akkaHttpVersion = "10.1.7"
val scalaTestVersion = "3.0.5"

libraryDependencies ++= Seq(
  // akka streams
  "com.typesafe.akka" %% "akka-stream" % akkaVersion,
  // akka http
  "com.typesafe.akka" %% "akka-http" % akkaHttpVersion,
  "com.typesafe.akka" %% "akka-http-spray-json" % akkaHttpVersion,
  "com.typesafe.akka" %% "akka-http-testkit" % akkaHttpVersion,
  // testing
  "com.typesafe.akka" %% "akka-testkit" % akkaVersion,
  "org.scalatest" %% "scalatest" % scalaTestVersion
)
keyno63 commented 2 years ago

@dr-jerry

Maybe, we can use jsonFormatN for class which contains primitive values only. If a class contains original class, should write/read function ourself. e.g. see Providing JsonFormats for unboxed types

case class Foo(i: Int, foo: Foo)

object PlayerJsonProtocol extends DefaultJsonProtocol {
  import OthersProtocols.FooProtocol
  implicit val fooFormat: JsonFormat[Foo] = lazyFormat(jsonFormat(Foo, "i", "foo"))
}

object OthersProtocols {
  implicit object FooProtocol extends JsonFormat[Foo] {
    def write(f: Foo): JsValue = JsObject(
      "i" -> JsNumber(f.i),
      "foo" -> write(f.foo)
    )
    def read(value: JsValue): Foo =
      value match {
        case JsObject(fields) =>
          (for {
            i <- fields.get("i").map(_.convertTo[Int])
            foo <- fields.get("foo").map(read)
          } yield Foo(i, foo)).get
        case JsNull => None.orNull
        case _ => deserializationError(s"unexpected json type for Foo, json: ${value}")
      }
  }
}

check by test

class PlayerJsonProtocolTest extends FunSuite {
  val jsonString = """
    |{
    |  "i": 1,
    |  "foo": {
    |    "i": 2,
    |    "foo": null
    |  }
    |}
    |""".stripMargin

  test("something test"){
    println(jsonString.parseJson.convertTo[Foo])
    assert(
      jsonString.parseJson.convertTo[Foo] ==
        Foo(1, Foo(2, None.orNull))
    )
  }
}
jrudolph commented 2 years ago

No. the above code works as intended. Maybe the issue is that using just Foo as a constructor isn't working all the time?

Try jsonFormat2(Foo.apply _) instead.

jrudolph commented 2 years ago

Maybe the issue is that using just Foo as a constructor isn't working all the time?

I.e. when Foo has a companion object. Without a companion object it works just as state above.