Reputation: 113
Using Spray Routing, I would like have a single directive that merges the query string parameters with a JSON entity, with both being optional. I would want to have this happen before any marshalling happens.
Something like this:
val myRoute = mergedParametersAndEntity(as[DomainSpecificClass]) { myobj =>
// ... code code code ...
complete(OK, myobj.someMethod)
}
Basically what I was hoping for was the following behavior:
When someone does a request like:
POST /v1/resource?a=helloQS&b=helloQS
Content-Type: application/json
{"a":"helloFromJson","c":"helloFromJson"}
Then the object above (myobj
) could contain the keys:
a -> helloFromJson
b -> helloQS
c -> helloFromJson
In other words, items specified in the request body would override things in the query string. I know this must be possible somehow, but I simply cannot figure out how to do it. Can anyone help?
Thank you!
Upvotes: 2
Views: 1121
Reputation: 1
If anyone is still wondering how to do this here's a small Spray directive that allows copying param values to the JSON before unmarshalling it. It uses JSON lenses (https://github.com/jrudolph/json-lenses) to modify the request body before unmarshalling.
def updateJson(update: Update): Directive0 = {
mapRequest((request: HttpRequest) => {
request.entity match {
case Empty => request
case NonEmpty(contentType, data) =>
try {
request.copy(entity = HttpEntity(`application/json`, JsonParser(data.asString).update(update).toString))
}
catch {
case e: ParsingException => request
}
}
})
}
And here's how you use it. Say you want to update a resource with a PUT request and pass the ID from the URL to the unmarshaller (a very common scenario btw):
// Model
case class Thing(id: Long, title: String, description: String, createdAt: DateTime)
// Actor message for the update operation
case class UpdateThing(id: Long, title: String)
// Spray routing
def thingsRoute =
path("things" / Segment) { id =>
put {
updateJson(field("id") ! set[Long](id)) {
entity(as[UpdateThing]) { updateThing =>
complete {
// updateThing now has the ID set
(someActor ? updateThing).mapTo[Thing]
}
}
}
}
}
You can also combine it with the parameters
directive to set arbitrary GET params.
Upvotes: 0
Reputation: 2621
My suggestion: don't take this approach. It feels dirty. Go back to the drawing board if you can.
With that in mind, you won't be able to interject a merge like you want with the existing JSON marshalling support in Spray. You'll need to stitch it together yourself. Something like this should at least point you in the right direction (provided it be the direction you must go):
import org.json4s.JsonAST._
import org.json4s.JsonDSL._
def mergedParametersAndEntity[T](implicit m:Manifest[T]): Directive1[T] = {
entity(as[JObject]).flatMap { jObject =>
parameterMap flatMap { params =>
val left = JObject(params.mapValues { v => JString(v) }.toSeq : _*)
provide(left.merge(jObject).extract[T])
}
}
}
Upvotes: 2