Kotlin is a programming language that runs on the Java Virtual Machine (JVM) and is designed to interoperate with Java code and thus with the existing frameworks.
For the purpose of this tutorial let’s imagine we develop a library application. As a first step let’s define a class that represents a book. A book has a name, author and description
1 2 |
class Book(val name: String, val author: String, val description: String) { } |
About the Kotlin language: More on classes
As a second step let’s marshal a Book
just to make sure everything is working:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
fun main(args: Array<String>) { val book = Book("Best Book Ever", "Best Author", "The name and the author say it all!") val jaxbContext = JAXBContext.newInstance(Book::class.java) val marshaller = jaxbContext.createMarshaller() marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true) val stringWriter = StringWriter() stringWriter.use { marshaller.marshal(book, stringWriter) } println(stringWriter) } |
About the Kotlin language: More on functions
Note: Here you can find more information on the Book::class.java
expression
Running the application shows an error stating that: unable to marshal type "Book" as an element because it is missing an @XmlRootElement annotation
Let’s add that annotation to the Book
class and run the application again – this time another error is printed: Book does not have a no-arg default constructor.
When we added the Book
class we declared only one constructor (the primary one) which has three arguments. Let’s add another one in order to make JAXB happy:
1 2 3 4 |
@XmlRootElement class Book(val name: String, val author: String, val description: String) { constructor() : this("No name", "No author", "No description") } |
The new constructor calls the primary one with default values. Kotlin has special opinion on null values (see here) and the developer must specially declare the null possibility. Since I agree that null should be avoided as a value, default values are provided in this case. Let’s run the application again. This time the output is:
1 2 |
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <book/> |
The book element is empty because we didn’t add @XmlElement to the fields we want to marshal and we did not tell JAXB how to access the properties. Let’s fix that:
1 2 3 4 5 |
@XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) class Book(@XmlElement val name: String, @XmlElement val author: String, @XmlElement val description: String) { constructor() : this("No name", "No author", "No description") } |
And here is the output:
1 2 3 4 5 6 |
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <book> <name>Best Book Ever</name> <author>Best Author</author> <description>The name and the author say it all!</description> </book> |
So far so good. We have a working marshaling of a Book
. Let’s organize books in a Library
by adding a new class that will hold a collection of Book
s:
1 2 3 4 5 6 7 |
@XmlRootElement class Library(@XmlAttribute val name: String) { constructor() : this("Unnamed Library") @XmlElement val books: MutableCollection<Book> = mutableListOf() } |
About the Kotlin language: More on collections
This time the library name won’t be an element but an attribute. Let’s marshal it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
fun main(args: Array<String>) { val jaxbContext = JAXBContext.newInstance(Library::class.java) val marshaller = jaxbContext.createMarshaller() marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true) val library = Library("My Library"); library.books.add(Book("Book 1", "Author 1", "Description 1")) library.books.add(Book("Book 2", "Author 2", "Description 2")) library.books.add(Book("Book 3", "Author 3", "Description 3")) val stringWriter = StringWriter() stringWriter.use { marshaller.marshal(library, stringWriter) } println(stringWriter) } |
And here is the output:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <library name="My Library"> <books> <name>Book 1</name> <author>Author 1</author> <description>Description 1</description> </books> <books> <name>Book 2</name> <author>Author 2</author> <description>Description 2</description> </books> <books> <name>Book 3</name> <author>Author 3</author> <description>Description 3</description> </books> </library> |
About the Kotlin language: More on try-with-resource
Let’s unmarshal it and print the contents of the Library
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
fun unmarshall() { val text = """ XML Goes Here """ val jaxbContext = JAXBContext.newInstance(Library::class.java) val unmarshaller = jaxbContext.createUnmarshaller() text.reader().use { it -> { val libray = unmarshaller.unmarshal(it) as Library println(libray.name) println(libray.books.size) println(libray.books.last().name) } } } |
(yes Kotlin supports multi-line strings. It’s omitted here in sake of brevity)
Note:Since the Unmarshaller returns Object
(not always but in this case it does) but we expect Library
object then we use “unsafe” cast with as
This is the output:
1 2 3 |
My Library 3 Book 3 |
Thanks, this is very helpful
Thank you, this was helpful! However, this does not work properly for me (with Kotlin 1.1.2-2): The @XmlElement annotations on the parameters of the Book class do not have any effect. You can see this by providing a name or a namespace (i.e. @XmlElement(name=”title”)). Luckily, annotations on properties (like you did in the Library class) work perfectly.
Thanks for the clarification.