xmlconfect is a type class based library to serialize Scala classes, in particular case classes, to XML / deserialize XML to Scala classes
It sports the following features:
- Based on Scala XML
- Type-class based (de)serialization of custom objects
- Supports reading / writing attributes or elements for basic types
- No external dependencies
xmlconfect is available from the Maven Central repository. The latest release is 0.3.5 and is built against Scala 2.11.7.
If you use SBT you can include xmlconfect in your project with
libraryDependencies += "com.mthaler" %% "xmlconfect" % "0.3.5"
xmlconfect is really easy to use. In the following we show how to serialize / deserialize a simple class case class Person(name: String, age: Int)
:
import com.mthaler.xmlconfect._
import com.mthaler.xmlconfect.BasicAttrFormats._
import com.mthaler.xmlconfect.ProductFormatInstances._
implicit val f = xmlFormat2(Person)
val p = Person("Albert Einstein", 42)
p.toNode
This will create an XML element with two attributes, name and age:
<Person name="Albert Einstein" age="42"/>
It is just as easy to deserialize XML:
import com.mthaler.xmlconfect._
import com.mthaler.xmlconfect.BasicAttrFormats._
import com.mthaler.xmlconfect.ProductFormatInstances._
implicit val f = xmlFormat2(Person)
<Person name="Albert Einstein" age="42"/>.convertTo[Person]
This will result in:
Person("Albert Einstein", 42)
To make it clear what happens in the above examples, we walk through them step by step. The first line
import com.mthaler.xmlconfect._
brings the toNode
and the convertTo
methods into scope.
import com.mthaler.xmlconfect.BasicAttrFormats._
brings attribute formats for basic Scala types like int, double and String into scope.
import com.mthaler.xmlconfect.ProductFormatInstances._
imports all methods from ProductFormatInstances which are used to create formats for case classes.
implicit val f = xmlFormat2(Person)
defines a format for the Person class.
val p = Person("Albert Einstein", 42)
p.toNode
creates a new person instance and calls the toNode
method to serialize it to XML. Instead of using implicits we could also pass the format directly using p.toNode(f)
.
<Person name="Albert Einstein" age="42"/>.convertTo[Person]
deserializes a class. Again, instead of using implicits, we could pass the format directly using <Person name="Albert Einstein" age="42"/>.convertTo[Person](f)
.
###Elements and Attriutes XML offers two ways to store data: elements and attributes. In the above example we used attributes to store the name and the age. But we could also use child elements to store them. xmlconfect supports both:
import com.mthaler.xmlconfect._
import com.mthaler.xmlconfect.BasicElemFormats._
import com.mthaler.xmlconfect.ProductFormatInstances._
implicit val f = xmlFormat2(Person)
val p = Person("Albert Einstein", 42)
p.toNode
This will create an XML element with two children, name and age:
<Person><name>Albert Einstein</name><age>42</age></Person>
The only difference is, that we import com.mthaler.xmlconfect.BasicElemFormats._
instead of com.mthaler.xmlconfect.BasicAttrFormats._
. To deserialize the class, we do
import com.mthaler.xmlconfect._
import com.mthaler.xmlconfect.BasicElemFormats._
import com.mthaler.xmlconfect.ProductFormatInstances._
implicit val f = xmlFormat2(Person)
<Person><name>Albert Einstein</name><age>42</age></Person>.convertTo[Person]
Again, the only difference is that we import com.mthaler.xmlconfect.BasicElemFormats._
instead of com.mthaler.xmlconfect.BasicAttrFormats._
.
###Default values
Sometimes we need to provide default values if e.g. an attribute is not present in XML. This could be the case if we save a config as XML and than read the config with a newer version of the program that added an aditional field to a class. For case classes, it is possible to use case class default values for missing attributes / elements. If we change our Person class to case class Person(name: String = "Name", age: Int = 21)
the following code
import com.mthaler.xmlconfect._
import com.mthaler.xmlconfect.BasicAttrFormats._
import com.mthaler.xmlconfect.ProductFormatInstances._
implicit val f = xmlFormat2(Person)
<Person name="Albert Einstein"/>.convertTo[Person]
will result in
Person(Albert Einstein, 21)
Important:_ this will only work if default values are specified for all fields of a case class.
###Collections It is quite common to serialize / deserialize collections of objects. It is easy to do this using xmlconfect:
import com.mthaler.xmlconfect._
import com.mthaler.xmlconfect.BasicAttrFormats._
import com.mthaler.xmlconfect.ProductFormatInstances._
import com.mthaler.xmlconfect.WrappedCollectionFormats.listFormat
implicit val f = xmlFormat2(Person)
val persons = List(Person("Albert Einstein", 42), Person("Richard Feyman", 28))
persons.toNode("Persons")
The result is
<Persons><Person name="Albert Einstein" age="42"/><Person name="Richard Feyman" age="28"/></Persons>
It is just as easy to deserialize the persons list:
import com.mthaler.xmlconfect._
import com.mthaler.xmlconfect.BasicAttrFormats._
import com.mthaler.xmlconfect.ProductFormatInstances._
import com.mthaler.xmlconfect.WrappedCollectionFormats.listFormat
implicit val f = xmlFormat2(Person)
<Persons>
<Person name="Albert Einstein" age="42"/>
<Person name="Richard Feyman" age="28"/>
</Persons>.convertTo[List[Person]]
In addition to the formats used to serialize / deserialize a person instance, we now import com.mthaler.xmlconfect.WrappedCollectionFormats.listFormat
which offers a format to serialize / deserialize lists. There are formats for several collection types:
- List
- Array
- immutable Iterable
- immutable Seq
- immutable IndexedSeq
- immutable LinearSeq
- immutable Set
- Vector
There is an overloaded version of toNode that allows us to specify a name for the element:
val persons = List(Person("Albert Einstein", 42), Person("Richard Feyman", 28))
persons.toNode("Persons")
We have to use the overloaded version here, otherwise the top-level element will have an empty name which is not valid XML.
It is also possible to serialize lists without wrapping them using
import com.mthaler.xmlconfect.CollectionFormats.listFormat
instead of
import com.mthaler.xmlconfect.WrappedCollectionFormats.listFormat
###Renaming elements It is sometimes necessary to rename elements, e.g. we have a list of Integer classes, but want to rename Integer to Int in the resulting XML. This is easy to do with xmlconfect:
import com.mthaler.xmlconfect._
import com.mthaler.xmlconfect.BasicAttrFormats._
import com.mthaler.xmlconfect.ProductFormat._
implicit val namedIntFormat = AdditionalFormats.namedFormat(xmlFormat1(Integer), "Int")
implicit val intListFomat = CollectionFormats.listFormat[Integer]
List(Integer(5), Integer(10), Integer(15)).toNode("Integers")
The result is:
<Integers><Int value="5"/><Int value="10"/><Int value="15"/></Integers>
It is easy to define custom formats with xmlconfect. To serialize / deserialize java.awt.Color
we could use the following format:
import com.mthaler.xmlconfect._
import java.awt.Color
import scala.xml.Node
import scala.xml.Null
import scala.xml.Text
implicit object ColorElemFormat extends SimpleXmlElemFormat[Color] {
protected def readElem(node: Node, name: String = ""): Color = {
val r = (node \ "@r").text.toInt
val g = (node \ "@g").text.toInt
val b = (node \ "@b").text.toInt
new Color(r, g, b)
}
protected override def writeElem(color: Color, name: String = ""): Node =
<Color r={ color.getRed.toString } g={ color.getGreen.toString }
b={ color.getBlue.toString }/>.copy(label = name)
}
Color.red.toNode("Color")
would then result in <Color r="255" g="0" b="0"/>
and <Color r="255" g="0" b="0"/>.convertTo[Color]
would create a Color object representing the color red.
It is just as easy to define an attribute format for java.awt.Color
:
import com.mthaler.xmlconfect._
import java.awt.Color
import scala.xml.MetaData
implicit object ColorXmlAttrFormat extends XmlAttrFormat[Color] {
protected def readAttr(metaData: MetaData, name: String = ""): Color =
new Color(metaData(name).text.toInt)
protected override def writeAttr(color: Color, name: String = ""): MetaData =
attribute(name, color.getRGB.toString)
}
##Credits Most of the code is inspired by (or just copied from) the excellent spray-json library.
##License xmlconfect is licensed under APL 2.0.