6
votes

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)
                                                ^
2

2 Answers

5
votes

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.

3
votes

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 = ???
}