Marshalling/unmarshalling XML in Scala
I am looking at various approaches for marshalling/unmarshalling data between Scala and XML, and I'm interested in getting community feedback (preferably grounded in first-hand knowledge/experience).
We're currently using JAXB, which is fine, but I'm hoping for a pure Scala solution. I'm considering the following approaches:
Use Scala's built-in XML facilities: Scala->XML would be easy, but my guess is that the other direction would be fairly painful. On the other hand, this approach supports arbitrary translation logic.
Data binding: scalaxb seems to be somewhat immature at the moment and doesn't handle our current schema, and I don't know of any other data binding library for Scala. Like JAXB, an extra translation layer is required to support involved transformations.
XML pickler combinators: The GData Scala Client library provides XML pickler combinators, but recent project activity has been low and I don't know what the current status is.
Questions:
- What are your experiences with the approaches/libraries I've listed?
- What are the relative advantages and disadvantages of each?
- Are there any other appr开发者_开发百科oaches or Scala libraries that I should consider?
Edit:
I added some notes on my early impressions of pickler combinators in my own answer to this question, but I'm still very interested in feedback from someone who actually knows the various approaches in depth. What I'm hoping for is a somewhat comprehensive comparison that would help developers choose the right approach for their needs.
I recommend using Scala's built-in XML features. I've just implemented deserialization for a document structure that looks like this:
val bodyXML = <body><segment uri="foo"><segment uri="bar" /></segment></body>
Note that the segments can be nested within each other.
A segment is implemented as follows:
case class Segment(uri: String, children: Seq[Segment])
To deserialize the XML, you do this:
val mySegments = topLevelSegments(bodyXML)
...and the implementation of topLevelSegments
is just a few lines of code. Note the recursion, which digs through the XML structure:
def topLevelSegments(bodyXML: Node): Seq[Segment] =
(bodyXML \ "segment") map { nodeToSegment }
def nodeToSegment = (n: Node) => Segment((n \ "@uri")(0) text, childrenOf(n))
def childrenOf(n: Node): Seq[Segment] = (n \ "segment") map { nodeToSegment }
Hope that helps.
For comparison, I implemented David's example using the pickler combinators from the GData Scala Client library:
def segment: Pickler[Segment] =
wrap(elem("segment",
attr("uri", text)
~ rep(segment))) { // rep = zero or more repetitions
// convert (uri ~ children) to Segment(uri, children), for unpickling
Segment.apply
} {
// convert Segment to (uri ~ children), for pickling
(s: Segment) => new ~(s.uri, s.children toList)
}
def body = elem("body", rep(segment))
case class Segment(uri: String, children: List[Segment])
This code is all that is necessary to specify both directions of the translation between Segment
s and XML, whereas a similar amount of code specifies only one direction of the translation when using the Scala XML library. In my opinion, this version is also easier to understand (once you know the pickler DSL). Of course, as David pointed out in a comment, this approach requires an additional dependency and another DSL that developers have to be familiar with.
Translating XML to Segments is as simple as
body.unpickle(LinearStore.fromFile(filename)) // returns a PicklerResult[List[Segment]]
and translating the other way looks like
xml.XML.save(filename, body.pickle(segments, PlainOutputStore.empty).rootNode)
As far as the combinator library is concerned, it seems to be in decent shape and compiles in Scala 2.8.1. My initial impression is that the library is missing a few niceties (e.g. a oneOrMore
combinator) that could be remedied fairly easily. I haven't had time to see how well it handles bad input, but so far it looks sufficient for my needs.
Writing a scala.xml.Node to a string isn't a big deal. PrettyPrinter
should take care of you needs. scala.xml.XML.save()
will write to a file and scala.xml.XML.write()
outputs to a Writer
.
精彩评论