Kotlin XML Binding
Eugene PetrenkoI like creating DSLs in Kotlin. It was fun to try creating a DSL for XML data binding. Of course there are so many other libraries for JVM that implements it in the other way.
I was looking for a way to use XPath like queries for data binding. I did not want to parse queries from raw strings. The DSL inherited some ideas from XPath queries.
In Kotlin I used Delegated Properties to achieve better readability, avoid explicit type names for serialization/deserialization rules.
Let's consider an example. Say you have an XML:
<project>
<settings type="q">
<name>Foo</name>
</settings>
<keys>
<key>A</key>
<key>B</key>
<key>C</key>
</keys>
</project>
Thanks to the kotlin.xml.bind, you may parse it with the following DSL in Kotlin:
class Project {
var type by JXML / XAttribute("type")
var name by JXML / "name" / XText
var keys by JXML / "keys" / XElements("key") / XText
var any by JXML / "parameters" / XAnyElements / XSub(Sub::class.java)
var unknownElements by JXML / XUnknown
}
Kotlin compiler infers types for properties, so one should not write types at all.
In the example above type
and name
properties are String?
,
keys
is List<String>?
. XAnyElements
means result will be as collection, XSub
parses sub-elements as XSub
objects.
kotlin.xml.bind provides serialization and deserialization.
Sources / Binaries
Sources are on GitHub under Apache 2.0 license. Binaries are published to a maven repo.
For more information, see kotlin.xml.bind project on GitHub.
Implementation details
I use JDOM to work with XML. DSL is done in separate module that does not depend on JDOM directly.
You may find API declarations here. For every DSL grammar rule of the DSL I created an interface in Kotlin.
The main trick is that expressions like JXML / "keys" / XElements("key") / XText
returns an object
that implements requirements of
Delegated Properties
.
The library adds a tiny overhead on every object creation. So now to create an object (e.g. Project
class above)
it has to create a number of objects from the DSL that are used behind delegated properties. Reflections
are still in use to scan available object properties too on serialization/deserialization.
For tests I created yet another DSL to generate XML easily. You may find more details in kotlin.xml.dsl on GitHub. This is an example of unit test
@Test
public fun test_read_any_element_does_not_include_parsed() {
class Data {
var X by JXML / "x" / XUnknown
var Y by JXML / "Y" / XUnknown
var Z by JXML / XAnyElements / XUnknown
}
val el = jdom("aaa") {
element("x") { text("yohoho")}
element("Y") { text("123")}
element("z") { text("www")}
element("p") { text("www")}
}
val d : Data = JDOM.load(el , Data::class.java)
Assert.assertEquals(d.Z?.size, 2)
Assert.assertEquals(d.Z?.get(0)?.name, "z")
Assert.assertEquals(d.Z?.get(1)?.name, "p")
}
I use Gradle as project model now. Artifacts are deployed to Bintray
Future Work
The ides of the DSL are easily mapped to JSON too. So it's possible to read/write/update JSON files in the same way Get rid of reflections and use Companion Objects Support other XML implementations, not only JDOM Improve, finalize and document binding DSL Invite contributors! Have more fun!