spray-json can't find JsonReader for type List[T]
Asked Answered
K

2

6

I'm creating custom json readers for case classes but it can't find implicit JsonReader type class for List[T] which is used in other case class.

When I checked DefaultJsonProtocol, it has implicit format for collections already;

  implicit def listFormat[T :JsonFormat] = new RootJsonFormat[List[T]] {
    def write(list: List[T]) = JsArray(list.map(_.toJson).toVector)
    def read(value: JsValue): List[T] = value match {
      case JsArray(elements) => elements.map(_.convertTo[T])(collection.breakOut)
      case x => deserializationError("Expected List as JsArray, but got " + x)
    }
  }

Here is the simplified code;

case class Test(i: Int, d: Double)
case class ListOfTest(t: List[Test])

trait TestResultFormat extends DefaultJsonProtocol {

  import CustomFormat._

    implicit object TestJsonFormat extends RootJsonReader[Test] {

    override def read(json: JsValue): Test = {

      val jsObject = json.asJsObject
      val jsFields = jsObject.fields

      val i = jsFields.get("i").map(_.convertTo[Int]).getOrElse(0)
      val d = jsFields.get("d").map(_.convertTo[Double]).getOrElse(0d)

      Test(i, d)
    }
  }

  implicit object ListOfTestJsonFormat extends RootJsonReader[ListOfTest] {

    override def read(json: JsValue): ListOfTest = {

      val jsObject = json.asJsObject
      val jsFields = jsObject.fields

      val tests = jsFields.get("hs").map(_.convertTo[List[Test]]).getOrElse(List.empty)

      ListOfTest(tests)
    }
  }

}

Here is the errors;

Error:(230, 53) not enough arguments for method convertTo: (implicit evidence$1: spray.json.JsonReader[List[com.xx.Test]])List[com.xx.Test].
Unspecified value parameter evidence$1.
      val tests = jsFields.get("hs").map(_.convertTo[List[Test]]).getOrElse(List.empty)
                                                    ^
Error:(230, 53) Cannot find JsonReader or JsonFormat type class for List[com.xx.Test]
      val tests = jsFields.get("hs").map(_.convertTo[List[Test]]).getOrElse(List.empty)
                                                ^
Kanpur answered 25/5, 2016 at 3:56 Comment(0)
S
5

I think the problem is related to the fact that the JsonReader for List[T] in DefaultJsonProtocol is a RootJsonFormat (not a RootJsonReader), which basically means you can read it and also write it. So, when you try to read a List[Item], it's expected that you are also able to write Items. So, you could use RootJsonFormat instead and throw an exception in case you try to write it (since you don't support it). For example:

import spray.json._

implicit object TestJsonFormat extends RootJsonFormat[Test] {

  override def read(json: JsValue): Test = {

    val jsObject = json.asJsObject
    val jsFields = jsObject.fields

    val i = jsFields.get("i").map(_.convertTo[Int]).getOrElse(0)
    val d = jsFields.get("d").map(_.convertTo[Double]).getOrElse(0d)

    Test(i, d)
  }

  override def write(obj: Test): JsValue = serializationError("not supported")
}

If you are aware of a clean solution involving only the readers, please do let me know because I ran into this problem myself and couldn't find anything else.

Spiegeleisen answered 25/5, 2016 at 5:56 Comment(2)
Thanks for the answer. I have just learned it from github issue. For the clean solution check my answer, I don't think its quite clean but I don't want to override write method always because we have lots of custom formatters, it's quite overhead so I created a base trait to do that.Kanpur
I see :) I had this problem long ago and I kind of got used to just overriding the (unused) write method. Thanks.Spiegeleisen
K
3

I have learned that limitation comes from spray-json:

spray-json 's type class infrastructure is build around the (Root)JsonFormat type, not the (Root)JsonReader. So you'll indeed have to provide a "Format" even if you are just reading.

Check here.

To overcome issue; I created another trait extends RootJsonFormat instead of reader and overrides write method with basically not implemented method.

trait EmptyWriterFormat[T] extends RootJsonFormat[T] {
  override def write(o: T): JsValue = ???
}
Kanpur answered 25/5, 2016 at 5:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.