6

I want to print data in xml format when department is "hr"but data in json format when department is "tech" .

Can we use spray-json Support https://doc.akka.io/docs/akka-http/current/common/json-support.html and XML support https://doc.akka.io/docs/akka-http/current/common/xml-support.html together

 private[rest] def route =
  (pathPrefix("employee") & get) {
    path(Segment) { id =>
      parameters('department ? "") { (flag) =>
        extractUri { uri =>
          complete {
            flag match {
              case "hr": =>  {

            HttpEntity(MediaTypes.`application/xml`.withCharset(HttpCharsets.`UTF-8`),"hr department")
            }
              case "tech" =>{
                HttpEntity(ContentType(MediaTypes.`application/json`), mapper.writeValueAsString("tech department"))

              }

            }
          }
        }
      }
    }
  }

Solution I tried I tried the below by using by using JsonProtocols and ScalaXmlSupport I get the compile error expected ToResponseMarshallable but found Department

case class department(name:String)
private[rest] def route =
  (pathPrefix("employee") & get) {
    path(Segment) { id =>
      parameters('department ? "") { (flag) =>
        extractUri { uri =>
          complete {
            flag match {
              case "hr": =>  {

            complete(department(name =flag))
            }
              case "tech" =>{
                complete(department(name =flag))

              }

            }
          }
        }
      }
    }
  }
coder25
  • 2,363
  • 12
  • 57
  • 104

2 Answers2

3

I think there are several issues you have to overcome to achieve what you want:

  1. You want to customize response type basing on the request parameters. It means standard implicit-based marshaling will not work for you, you'll have to do some explicit steps

  2. You want to marshal into an XML-string some business objects. Unfortunately, ScalaXmlSupport that you referenced does not support this, it can marshal only an XML-tree into a response. So you'll need some library that can do XML serialization. One option would be to use jackson-dataformat-xml with jackson-module-scala. It also means you'll have to write your own custom Marshaller. Luckily it is not that hard.

So here goes some simple code that might work for you:

import akka.http.scaladsl.marshalling.{ToResponseMarshallable, Marshaller}

// json marshalling
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import spray.json._
import spray.json.DefaultJsonProtocol._
implicit val departmentFormat = DefaultJsonProtocol.jsonFormat1(department)
val departmentJsonMarshaller = SprayJsonSupport.sprayJsonMarshaller[department]

// xml marshalling, need to write a custom Marshaller
// there are several MediaTypes for XML such as `application/xml` and `text/xml`, you'll have to choose the one you need.
val departmentXmlMarshaller = Marshaller.StringMarshaller.wrap(MediaTypes.`application/xml`)((d: department) => {
  import com.fasterxml.jackson.dataformat.xml.XmlMapper
  import com.fasterxml.jackson.module.scala.DefaultScalaModule
  val mapper = new XmlMapper()
  mapper.registerModule(DefaultScalaModule)
  mapper.writeValueAsString(d)
})


private val route =
  (pathPrefix("employee") & get) {
    path(Segment) { id =>
      parameters('department ? "") { (flag) =>
        extractUri { uri => {
          flag match {
            case "hr" => {
              // explicitly use the XML marshaller 
              complete(ToResponseMarshallable(department(name = flag))(departmentXmlMarshaller))
            }
            case "tech" => {
              // explicitly use the JSON marshaller 
              complete(ToResponseMarshallable(department(name = flag))(departmentJsonMarshaller))
            }
          }
        }
        }
      }
    }
  }

Note that for Jackson XML serializer to work correctly the department class should be a top level class or you'll get a cryptic error about bad root name.

SergGr
  • 23,570
  • 2
  • 30
  • 51
1

Akka Http already has built in content type negotiation. Ideally you should just use that by having a marshaller that knows how to turn your department into either xml or json and having the client set the Accept header.

However it sounds like maybe you can't get your client to do that, so here's what you can do, assuming you have already made a ToEntityMarshaller[department] for both xml and json using ScalaXmlSupport and SprayJsonSupport.

val toXmlEntityMarshaller: ToEntityMarshaller[department] = ???
val toJsonEntityMarshaller: ToEntityMarshaller[department] = ???
implicit val departmentMarshaller = Marshaller.oneOf(toJsonEntityMarshaller, toXmlEntityMarshaller)

def route =
  parameters("department") { departmentName =>
    // capture the Accept header in case the client did request one
    optionalHeaderValueByType[Accept] { maybeAcceptHeader => 
      mapRequest ( _
        .removeHeader(Accept.name)
        // set the Accept header based on the department
        .addHeader(maybeAcceptHeader.getOrElse(
          Accept(departmentName match {
            case "hr" ⇒ MediaTypes.`application/xml`
            case "tech" ⇒ MediaTypes.`application/json`
          })
        ))
      ) (
        // none of our logic code is concerned with the response type
        complete(department(departmentName))
        )
    }
  }
kag0
  • 5,624
  • 7
  • 34
  • 67
  • This is an interesting approach. And it seems that if the client not just misses the Accept header but sends a wrong one (for example accepting `text/html`), you will have to remove `maybeAcceptHeader.getOrElse` or you need a deeper modification of the contents of the header by adding `application/xml` or `application/json` to the end of the list with low priority but probably above the `*/*`. – SergGr Jul 20 '18 at 10:03
  • It's true that this would 406 if the client sent something like a `text/html` Accept header. However I think if a client is explicitly telling you it only knows how to deal with HTML, then sending it back some JSON doesn't make much sense. – kag0 Jul 23 '18 at 16:46