0

I am creating a service that aggregates data and will need to be able to read any unknown JSON document. I have the pipeline defined as follows:

  private def pipeline = (
       addHeader("Accept", "application/json")
    ~> sendReceive
    ~> unmarshal[JsObject] // Need this to work for JsObject or JsArray //
    ~> recover
  )

This will work with a JsObject but not a JsArray. If I change it to a JsArray then it will not (of course) work with a JsObject. My recover method returns a JsObject.

I would love to be able to define this as a JsValue or enforce a Root format, but for JsValue I get the following compiler error:

could not find implicit value for evidence parameter of type spray.httpx.unmarshalling.FromResponseUnmarshaller[spray.json.JsValue]

And Root Formats also error.

I am not sure how to accomplish what I need, any help would be appreciated.

Eric
  • 127
  • 7
  • To satisfy my goal, I went with a variation of my stated answer below. Mustafa answered the exact question as asked, so I chose his as the SO answer. Thanks again @Mustafa! – Eric Jan 26 '16 at 02:00

2 Answers2

1

Use Either, Eric! :) If the response will be either JsObject or JsArray then Either is good solution.

private def pipeline =
       addHeader("Accept", "application/json")
    ~> sendReceive
    ~> unmarshal[Either[JsObject, JsArray]]
    ~> recover

However, beware that unmarshal[Either[JsObject, JsArray]] tries to parse response as JsObject first and if it fails, tries to parse it as JsArray. This may lead some performance issues.

Mustafa Simav
  • 1,019
  • 5
  • 6
  • Thanks @Mustafa! This is and interesting approach. I'll have to give this a go. While tactically Either may be the best approach, I really simply need a JSON AST, which should not have to fail one case to check the other. Don't get me wrong you answered my specific question and I can't wait to give it a go, as I try to solve it in a way that would not have the hit. The actual hit, may also be fairly small so that would be good to check too. – Eric Jan 25 '16 at 15:07
  • I am inclined to accept your answer because it answers my question exactly. The alternative solution may achieve my goal with some refinement. Thanks! – Eric Jan 25 '16 at 15:26
1

After reviewing @Mustafa's answer I created the following to avoid the potential performance hit. In the end, I really only need a JSON AST to pass on.

In the most simple terms, I simply created a function to handle it:

  def unmarshalJSON(httpResponse: HttpResponse): JsValue = {
    httpResponse.entity.asString.parseJson
  }

and altered below:

private def pipeline = {
     addHeader("Accept", "application/json")
  ~> sendReceive
  ~> unmarshalJSON
  ~> recover
}

I would of course want to beef this up a bit for production level code, but this could be another alternative and allows me to return a JsValue. @Mustafa I would be interested to hear your thoughts.

Eric
  • 127
  • 7
  • It looks good to me. You may want to add some status code checks like original implementation: https://github.com/spray/spray/blob/master/spray-httpx/src/main/scala/spray/httpx/ResponseTransformation.scala#L30-L39 – Mustafa Simav Jan 26 '16 at 08:03