Sam in Oakland
Sam in Oakland

Reputation: 113

Combine Query String Parameters with JSON Entity in Spray 1.2.0 Routing

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

Answers (2)

eugenijusr
eugenijusr

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

Tim Harper
Tim Harper

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

Related Questions