0

I am currently struggling with spray-json writing a protocol for my data model. For deserialization of JSON data to my data transfer objects, a DAO has to be contacted to check if an appropriate object exists, otherwise a DeserializationException should be thrown.

So far, I have the following:

object MyJsonProtocol extends DefaultJsonProtocol {

  implicit object MyDtoJsonFormat extends RootJsonFormat[MyDto] {

    override def write(obj: MyDto): JsValue = // Serialization implementation

    override def read(json: JsValue): MyDto = {
      // parse the JSON, get some parameters, let them be a, b, c
      dtoLookup(a, b, c) match {
        case Some(dto: MyDto) => dto
        case None => throw new DeserializationException("Cannot retrieve object from DAO")
      }
    }
  }

  def dtoLookup(a: SomeType, b: SomeOtherType, c: YetAnotherType)(implicit dao: MyDAO): Option[MyDto] = {
    // lookup the MyDTO with the dao instance
  }
}

My test looks like the following:

class MyJsonProtocolTest extends FlatSpec with Matchers {

  implicit val MyDAO = // some test instance, can be a mock object

  "The protocol" should "serialize a DTO" in {
    val dto: MyDTO = ...
    dto.toJson.compactPrint should be("{...}")
  }
}

However, the compiler complains that it cannot find the implicit MyDAO when trying to compile the MyJSONProtocol. In When testing Spray services with Scalatest, how to introduce implicit values? I asked yesterday, I was suggested to pass in the implicit parameter directly into the method, but I cannot do this here because the read method is defined in the RootJsonFormat.

When I call the dtoLookup method directly from my test code, it succeeds.

So, how do I get the MyDAO instance into my special JSON format?

Community
  • 1
  • 1
rabejens
  • 7,594
  • 11
  • 56
  • 104

1 Answers1

0

One option is to make the implicit parameter a constructor parameter to one of the classes being used. This might require that you turn one of your objects into a class. Then you can make an get method on the companion object of that class that uses an implicit in scope to construct the class with the desired argument.

This doesn't really have to do with spray or scalatest, rather it's just an issue with implicits and implicit scope. Here's a simplified version:

object MyJsonProtocol {
  implicit object MyDtoJsonFormat {
    def read(x: Int) = dtoLookup
  }
  def dtoLookup(implicit x: Int) = x + 1
}

And you might consider changing that to:

class MyJsonProtocol(implicit x: Int) {
  implicit object MyDtoJsonFormat {
    def read(x: Int) = dtoLookup
  }
  def dtoLookup = x + 1
}

object MyJsonProtol {
    def get(implicit x: Int) = new MyJsonProtocol
}

And then you can use this with an implicit in scope:

class MyJsonProtocolTest {
  implicit val x = 5
  val proto = MyJsonProtol.get
  val myReadValue = proto.MyDtoJsonFormat.read //6
}

You can read about the rules for implicit scopes here, especially relevant might be the "Where do Implicits Come From" section.

Community
  • 1
  • 1
Ben Reich
  • 16,222
  • 2
  • 38
  • 59
  • Thanks, I will try that. But for the problem I wanted to solve I found another solution which I consider "cleaner". I created a "Bridge" `MyDTOBridge` which is a case class and contains all parameters I want to (de)serialize, and use this in my JSON protocol. Then I created a `MyDTOConverter` which has implicit methods converting the `MyDTO` to its bridge and back, where I use the DAO as an implicit parameter. I can then get the `MyDTO` in an `Option` where I can get `None` when the DTO does not exist in the DAO. – rabejens Apr 26 '15 at 08:47
  • Another question: How do I have to understand the `get` method? Is this called implicitly when I do `new MyJSONProtocol`? Is this similar to `apply`? – rabejens Apr 26 '15 at 08:57