Search code examples
scalasprayscalatestspray-json

Unable to find JSONReader for parameterised-typed custom class in test, despite import


I have a custom class as follows

object SafeList {
  def apply[A](x: List[A]): SafeList[A] = if (x == null) EmptyList else HasItems[A](x)
}

sealed abstract class SafeList[+A] extends Product with Serializable {
  def get: List[A]
}

final case class HasItems[+A](x: List[A]) extends SafeList[A] {
  def get = x
}

case object EmptyList extends SafeList[Nothing] {
  def get = Nil
}

And a formatter for the SafeList which looks like this

...
import spray.json.DefaultJsonProtocol._

trait SafeCollectionFormats {
  implicit def safeListFormat[A: RootJsonFormat] = new RootJsonFormat[SafeList[A]] {

    def read(json: JsValue): SafeList[A] = {
      val list: List[A] = listFormat[A].read(json)
      SafeList[A](list)
    }

    def write(sl: SafeList[A]): JsValue =
      listFormat[A].write(sl.get)
  }
}
object SafeCollectionFormats extends SafeCollectionFormats

And that compiles.

But when I add a test for my formatter, like so....

...
import spray.json.DefaultJsonProtocol._
import marshalling.SafeCollectionFormats._
...

  "Unmarshalling a json array with items" should "produce a SafeList with items" in {
    val json: JsValue = JsArray(JsString("a"), JsString("b"), JsString("c"))
    val list = List("a", "b", "c")

    val result = json.convertTo[SafeList[String]]

    assertResult(list)(result)
  }

...

I get the following compilation error

Error:(14, 32) Cannot find JsonReader or JsonFormat type class for myapp.types.SafeList[String]
    val result = json.convertTo[SafeList[String]]
                               ^

I think there may be something in this answer to help me but it's a bit advanced for me. I thought my implicit safeListFormat was the JsonReader for my SafeList and i'm importing it into my Spec. I dont know if the parameterised types are confusing things?

Any ideas what i'm doing wrong?

Edit: Whilst my test at the moment is creating a SafeList of Strings, the ultimate intention is to create a SafeList of my domain objects. I will need to add a second test that builds a JsArray of MyObjects. And so the type of A - in JSON terms will be different. My SafeList needs to cope with both simple objects like Strings, and domain objects. I think I might raise this as a second SO question but I mention it here for context


Solution

  • It works for me with just one little change: I made SafeCollectionFormats extend DefaultJsonProtocol.

    I also had to change the context bound for your safeListFormat to [A: JsonFormat].

    trait SafeCollectionFormats extends DefaultJsonProtocol {
      implicit def safeListFormat[A: JsonFormat] = new RootJsonFormat[SafeList[A]] {
    
        def read(json: JsValue): SafeList[A] = {
          val list: List[A] = listFormat[A].read(json)
          SafeList[A](list)
        }
    
        def write(sl: SafeList[A]): JsValue =
          listFormat[A].write(sl.get)
      }
    }
    
    object SafeCollectionFormats extends SafeCollectionFormats
    

    Hope this helps you.