GitHub Maven Central Version Scaladoc

xmlwriter

Macro-powered fast and easy XML serialization library for Scala 3.

Table of contents

Example usage

import org.encalmo.writer.xml.XmlWriter

case class Address(
    street: String,
    city: String,
    postcode: String
)

case class Employee(
    name: String,
    age: Int,
    email: Option[String],
    addresses: List[Address],
    active: Boolean
)

val entity = Employee(
    name = "John Doe",
    age = 30,
    email = Some("john.doe@example.com"),
    addresses = List(
    Address(street = "123 Main St", city = "Anytown", postcode = "12345"),
    Address(street = "456 Back St", city = "Downtown", postcode = "78901")
    ),
    active = true
)

val xml = XmlWriter.writeIndented(entity)
println(xml)

Output:

<?xml version='1.0' encoding='UTF-8'?>
<Employee>
    <name>John Doe</name>
    <age>30</age>
    <email>john.doe@example.com</email>
    <addresses>
        <Address>
            <street>123 Main St</street>
            <city>Anytown</city>
            <postcode>12345</postcode>
        </Address>
        <Address>
            <street>456 Back St</street>
            <city>Downtown</city>
            <postcode>78901</postcode>
        </Address>
    </addresses>
    <active>true</active>
</Employee>

The example above produces the following inlined code:

val xml: String = {

    org.encalmo.writer.xml.XmlWriter.writeIndented[Employee]{
      final lazy given val builder:
        org.encalmo.writer.xml.XmlOutputBuilder.IndentedXmlStringBuilder =
        org.encalmo.writer.xml.XmlOutputBuilder.indented(
          indentation = 4,
          initialString = if addXmlDeclaration then
              "<?xml version=\'1.0\' encoding=\'UTF-8\'?>" else ""
        )
      
        builder.appendElementStart("Employee", Nil)
        
        def writeCaseClassToXml_Employee(): Unit = {

            builder.appendElementStart("name")
            builder.appendText(entity.name.toString())
            builder.appendElementEnd("name")

            builder.appendElementStart("age")
            builder.appendText(entity.age.toString())
            builder.appendElementEnd("age")
              
            entity.email match {
                  case Some(value) =>
                      builder.appendElementStart("email")
                      builder.appendText(value.toString())
                      builder.appendElementEnd("email")

                  case None =>
                    ()
            }
            
            builder.appendElementStart("addresses")

            entity.addresses.iterator.foreach( (value: Address) => {
                    builder.appendElementStart("Address", Nil)
                    
                    def writeCaseClassToXml_Address(): Unit ={
                        builder.appendElementStart("street")
                        builder.appendText(value.street.toString()
                        builder.appendElementEnd("street")

                        builder.appendElementStart("city")
                        builder.appendText(value.city.toString())
                        builder.appendElementEnd("city")

                        builder.appendElementStart("postcode")
                        builder.appendText(value.postcode.toString())
                        builder.appendElementEnd("postcode")
                    }

                    writeCaseClassToXml_Address()
                    builder.appendElementEnd("Address")
                }
            )
            builder.appendElementEnd("addresses")
              
            builder.appendElementStart("active")
            builder.appendText(entity.active.toString())
            builder.appendElementEnd("active")
        }

        writeCaseClassToXml_Employee()
        builder.appendElementEnd("Employee")
    }
    builder.result
}
println(xml)

Outstanding features

Scala types supported directly without the need for typeclass derivation

Supported Java types

Supported annotations

Annotation Description
@xmlAttribute Marks the target to be serialized as an XML attribute of the enclosing element rather than as a child.
@xmlContent Marks target as the content (text value) of the XML element instead of a tag or attribute.
@xmlTag Sets a custom XML tag or attribute name for this target (overrides the target name in serialization).
@xmlItemTag Specifies the tag name to use for each element in a collection or array.
@xmlNoItemTags Prevents wrapping each collection element in an extra XML tag; all items are added directly.
@xmlNoTagInsideCollection Omits the tags of the target when inside a collection or an array.
@xmlUseEnumCaseNames Sets the case name of an enum as the XML element tag when serializing the enum value instead of field name or enum type name.
@xmlValue Defines a static value for an element, useful for enum cases
@xmlValueSelector Selects which member/field/property from a nested type is used as the value/text for this element.

Key abstractions

Dependencies

Usage

Use with SBT

libraryDependencies += "org.encalmo" %% "xmlwriter" % "0.9.3"

or with SCALA-CLI

//> using dep org.encalmo::xmlwriter:0.9.3

More examples

Example with nested case classes and optional fields:

import org.encalmo.writer.xml.XmlWriter

case class Address(
  street: String,
  city: String,
  postcode: String,
  country: Option[String] = None
)

case class Company(
  name: String,
  address: Address
)

case class Employee(
  name: String,
  age: Int,
  email: Option[String],
  address: Option[Address],
  company: Option[Company]
)

val employee = Employee(
  name = "Alice Smith",
  age = 29,
  email = Some("alice.smith@company.com"),
  address = Some(
    Address(
      street = "456 Market Ave",
      city = "Metropolis",
      postcode = "90210",
      country = None
    )
  ),
  company = Some(
    Company(
      name = "Acme Widgets Inc.",
      address = Address(
        street = "123 Corporate Plaza",
        city = "Metropolis",
        postcode = "90211",
        country = Some("USA")
      )
    )
  )
)

// Serialize as indented XML (with XML declaration)
val xml: String = XmlWriter.writeIndented(employee)
println(xml)

Output:

<?xml version='1.0' encoding='UTF-8'?>
<Employee>
    <name>Alice Smith</name>
    <age>29</age>
    <email>alice.smith@company.com</email>
    <address>
        <street>456 Market Ave</street>
        <city>Metropolis</city>
        <postcode>90210</postcode>
    </address>
    <company>
        <name>Acme Widgets Inc.</name>
        <address>
            <street>123 Corporate Plaza</street>
            <city>Metropolis</city>
            <postcode>90211</postcode>
            <country>USA</country>
        </address>
    </company>
</Employee>
// Example: Serialize a case class with collections and XML annotations

import org.encalmo.writer.xml.XmlWriter
import org.encalmo.writer.xml.annotation.{xmlAttribute, xmlItemTag, xmlTag}

case class Tag(
  @xmlAttribute name: String,
  value: String
)

@xmlTag("Bookshelf")
case class Library(
  @xmlAttribute libraryId: String,
  name: String,
  @xmlItemTag("Book") books: List[Book]
)

case class Book(
  @xmlAttribute isbn: String,
  title: String,
  author: String,
  tags: List[Tag]
)

val library = Library(
  libraryId = "lib123",
  name = "City Library",
  books = List(
    Book(
      isbn = "978-3-16-148410-0",
      title = "Programming Scala",
      author = "Dean Wampler",
      tags = List(
        Tag(name = "Scala", value = "Functional"),
        Tag(name = "Programming", value = "JVM")
      )
    ),
    Book(
      isbn = "978-1-61729-065-7",
      title = "Functional Programming in Scala",
      author = "Paul Chiusano",
      tags = List(
        Tag(name = "Scala", value = "FP"),
        Tag(name = "Education", value = "Advanced")
      )
    )
  )
)

val xml: String = XmlWriter.writeIndented(library)
println(xml)

Output:

<?xml version='1.0' encoding='UTF-8'?>
<Bookshelf libraryId="lib123">
    <name>City Library</name>
    <books>
        <Book isbn="978-3-16-148410-0">
            <title>Programming Scala</title>
            <author>Dean Wampler</author>
            <tags>
                <Tag name="Scala">Functional</Tag>
                <Tag name="Programming">JVM</Tag>
            </tags>
        </Book>
        <Book isbn="978-1-61729-065-7">
            <title>Functional Programming in Scala</title>
            <author>Paul Chiusano</author>
            <tags>
                <Tag name="Scala">FP</Tag>
                <Tag name="Education">Advanced</Tag>
            </tags>
        </Book>
    </books>
</Bookshelf>

Project content

├── .github
│   └── workflows
│       ├── pages.yaml
│       ├── release.yaml
│       └── test.yaml
│
├── .gitignore
├── .scalafmt.conf
├── annotation.scala
├── LICENSE
├── MacroXmlWriter.scala
├── MacroXmlWriterSpec.test.scala
├── Order.java
├── project.scala
├── README.md
├── Status.java
├── test.sh
├── TestData.test.scala
├── TestModel.test.scala
├── XmlOutputBuilder.scala
└── XmlWriter.scala