XML
Adding XML encoding/decoding support is a matter of providing a body serializer and/or a response body specification (similarly as it is done for JSON format). The process of adding integrations is fairly easy, and for now, one guide on how to use scalaxb tool is provided.
scalaxb
If you possess the XML Schema definition file (.xsd
file) consider using the scalaxb tool, which would generate needed models and serialization/deserialization logic. To use the tool please follow the documentation on setting up and running scalaxb.
After code generation, create an SttpScalaxbApi
trait (or trait with another name of your choosing) and add the following code snippet:
import generated.defaultScope // import may differ depending on location of generated code
import scalaxb.`package`.{fromXML, toXML} // import may differ depending on location of generated code
import scalaxb.{CanWriteXML, XMLFormat} // import may differ depending on location of generated code
import sttp.client4.{ResponseAs, ResponseException, StringBody, asString}
import sttp.model.MediaType
import scala.xml.{NodeSeq, XML}
trait SttpScalaxbApi:
case class XmlElementLabel(label: String)
// request body
def asXml[B](b: B)(implicit format: CanWriteXML[B], label: XmlElementLabel): StringBody =
val nodeSeq: NodeSeq = toXML[B](obj = b, elementLabel = label.label, scope = defaultScope)
StringBody(nodeSeq.toString(), "utf-8", MediaType.ApplicationXml)
private def deserializeXml[B](implicit decoder: XMLFormat[B]): String => Either[Exception, B] =
(s: String) =>
try
Right(fromXML[B](XML.loadString(s)))
catch
case e: Exception => Left(e)
// response body handling description
def asXml[B: XMLFormat]: ResponseAs[Either[ResponseException[String], B], Any] =
asString.mapWithMetadata(ResponseAs.deserializeRightWithError(deserializeXml[B]))
.showAs("either(as string, as xml)")
This would add asXml
methods needed for serialization and deserialization. Please notice, that fromXML
, toXML
, CanWriteXML
, XMLFormat
and defaultScope
are members of code generated by scalaxb.
Next to this trait, you might want to introduce sttpScalaxb
package object to simplify imports.
package object sttpScalaxb extends SttpScalaxbApi
From now on, XML serialization/deserialization would work for all classes generated from .xsd
file as long as XMLFormat
for the type in the question and XmlElementLabel
for the top XML node would be implicitly provided in the scope.
Usage example:
val backend: SyncBackend = DefaultSyncBackend()
// `Outer` and `Inner` classes are generated by scalaxb from xsd file
val requestPayload = Outer(Inner(42, b = true, "horses"), "cats")
// imports sttp related serialization / deserialization logic
import sttpScalaxb.*
// gives needed XmlElementLabel for the top XML node
given XmlElementLabel = XmlElementLabel("outer")
// imports member of code generated by scalaxb, that provides `XMLFormat` for `Outer` type;
// this import may differ depending on location of generated code
import generated.Generated_OuterFormat
val response: Response[Either[ResponseException[String], Outer]] =
basicRequest
.post(uri"...")
.body(asXml(requestPayload))
.response(asXml[Outer])
.send(backend)