{"id":42379921,"url":"https://github.com/encalmo/xmlwriter","last_synced_at":"2026-03-01T18:01:03.546Z","repository":{"id":334936635,"uuid":"1142879100","full_name":"encalmo/xmlwriter","owner":"encalmo","description":"Macro-powered fast XML serialization library for Scala 3.","archived":false,"fork":false,"pushed_at":"2026-02-26T21:56:57.000Z","size":238,"stargazers_count":9,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-27T03:43:32.347Z","etag":null,"topics":["macros","scala3","serialization","xml"],"latest_commit_sha":null,"homepage":"https://encalmo.github.io/xmlwriter/","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/encalmo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-27T00:39:38.000Z","updated_at":"2026-02-26T21:57:01.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/encalmo/xmlwriter","commit_stats":null,"previous_names":["encalmo/xmlwriter"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/encalmo/xmlwriter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/encalmo%2Fxmlwriter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/encalmo%2Fxmlwriter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/encalmo%2Fxmlwriter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/encalmo%2Fxmlwriter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/encalmo","download_url":"https://codeload.github.com/encalmo/xmlwriter/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/encalmo%2Fxmlwriter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29974708,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-01T15:41:30.362Z","status":"ssl_error","status_checked_at":"2026-03-01T15:37:07.343Z","response_time":124,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["macros","scala3","serialization","xml"],"created_at":"2026-01-27T21:21:35.484Z","updated_at":"2026-03-01T18:01:03.491Z","avatar_url":"https://github.com/encalmo.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ca href=\"https://github.com/encalmo/xmlwriter\"\u003e![GitHub](https://img.shields.io/badge/github-%23121011.svg?style=for-the-badge\u0026logo=github\u0026logoColor=white)\u003c/a\u003e \u003ca href=\"https://central.sonatype.com/artifact/org.encalmo/xmlwriter_3\" target=\"_blank\"\u003e![Maven Central Version](https://img.shields.io/maven-central/v/org.encalmo/xmlwriter_3?style=for-the-badge)\u003c/a\u003e \u003ca href=\"https://encalmo.github.io/xmlwriter/scaladoc/org/encalmo/writer/xml.html\" target=\"_blank\"\u003e\u003cimg alt=\"Scaladoc\" src=\"https://img.shields.io/badge/docs-scaladoc-red?style=for-the-badge\"\u003e\u003c/a\u003e\n\n# xmlwriter\n\nMacro-powered fast and easy XML serialization library for Scala 3.\n\n## Table of contents\n\n- [Example usage](#example-usage)\n- [Outstanding features](#outstanding-features)\n- [Scala types supported directly without the need for typeclass derivation](#scala-types-supported-directly-without-the-need-for-typeclass-derivation)\n- [Supported Java types](#supported-java-types)\n- [Supported annotations](#supported-annotations)\n- [Key abstractions](#key-abstractions)\n- [How do we tag elements?](#how-do-we-tag-elements?)\n   - [Root element tag](#root-element-tag)\n- [Nested elements](#nested-elements)\n- [Dependencies](#dependencies)\n- [Usage](#usage)\n- [More examples](#more-examples)\n- [Project content](#project-content)\n\n## Example usage\n\n```scala\nimport org.encalmo.writer.xml.XmlWriter\n\ncase class Address(\n    street: String,\n    city: String,\n    postcode: String\n)\n\ncase class Employee(\n    name: String,\n    age: Int,\n    email: Option[String],\n    addresses: List[Address],\n    active: Boolean\n)\n\nval entity = Employee(\n  name = \"John Doe\",\n  age = 30,\n  email = Some(\"john.doe@example.com\"),\n  addresses = List(\n    Address(street = \"123 Main St\", city = \"Anytown\", postcode = \"12345\"),\n    Address(street = \"456 Back St\", city = \"Downtown\", postcode = \"78901\")\n  ),\n  active = true\n)\n\nval xml = XmlWriter.writeIndented(entity)\nprintln(xml)\n```\nOutput:\n```xml\n\u003c?xml version='1.0' encoding='UTF-8'?\u003e\n\u003cEmployee\u003e\n    \u003cname\u003eJohn Doe\u003c/name\u003e\n    \u003cage\u003e30\u003c/age\u003e\n    \u003cemail\u003ejohn.doe@example.com\u003c/email\u003e\n    \u003caddresses\u003e\n        \u003cAddress\u003e\n            \u003cstreet\u003e123 Main St\u003c/street\u003e\n            \u003ccity\u003eAnytown\u003c/city\u003e\n            \u003cpostcode\u003e12345\u003c/postcode\u003e\n        \u003c/Address\u003e\n        \u003cAddress\u003e\n            \u003cstreet\u003e456 Back St\u003c/street\u003e\n            \u003ccity\u003eDowntown\u003c/city\u003e\n            \u003cpostcode\u003e78901\u003c/postcode\u003e\n        \u003c/Address\u003e\n    \u003c/addresses\u003e\n    \u003cactive\u003etrue\u003c/active\u003e\n\u003c/Employee\u003e\n```\n\nThe example above produces the following code after macro expansion:\n```scala\n{\n  val builder: org.encalmo.writer.xml.XmlOutputBuilder = ...\n  builder.appendElementStart(\"Employee\", immutable.Nil)\n\n  def writeCaseClassToXml_Address(address: Address): scala.Unit = {\n    builder.appendElementStart(\"street\")\n    builder.appendText(address.street)\n    builder.appendElementEnd(\"street\")\n    builder.appendElementStart(\"city\")\n    builder.appendText(address.city)\n    builder.appendElementEnd(\"city\")\n    builder.appendElementStart(\"postcode\")\n    builder.appendText(address.postcode)\n    builder.appendElementEnd(\"postcode\")\n  }\n\n  def writeCaseClassToXml_Employee(employee: Employee): scala.Unit = {\n    builder.appendElementStart(\"name\")\n    builder.appendText(employee.name)\n    builder.appendElementEnd(\"name\")\n    builder.appendElementStart(\"age\")\n    builder.appendText(employee.age.toString())\n    builder.appendElementEnd(\"age\")\n\n    employee.email match {\n      case string: scala.Some[scala.Predef.String] =\u003e\n        builder.appendElementStart(\"email\")\n        builder.appendText(string.value)\n        builder.appendElementEnd(\"email\")\n      case scala.None =\u003e\n        ()\n    }\n\n    builder.appendElementStart(\"addresses\")\n    val addressesIterator: scala.collection.Iterator[Address] = (employee.addresses: scala.collection.Iterable[Address]).iterator\n    while (addressesIterator.hasNext) {\n      val addressItem: Address = addressesIterator.next()\n      builder.appendElementStart(\"Address\", immutable.Nil)\n      writeCaseClassToXml_Address(addressItem)\n      builder.appendElementEnd(\"Address\")\n      ()\n    }\n    builder.appendElementEnd(\"addresses\")\n\n    builder.appendElementStart(\"active\")\n    builder.appendText(employee.active.toString())\n    builder.appendElementEnd(\"active\")\n  }\n  \n  writeCaseClassToXml_Employee(entity)\n  builder.appendElementEnd(\"Employee\")\n}\n```\n\n## Outstanding features\n- **Generates highly performant low-level code** \n- Supports **field, value, case, and type annotations** enabling fine-tuning of the resulting XML,\n- Supports **custom tag and attribute name transformation** (e.g., snake_case, kebab-case, upper/lower case, etc),\n- **Indented or compact XML output** with pluggable output builders (including streaming),\n- Automatic **escaping of text** (element and attribute content) to produce well-formed XML.\n- Extensible to custom types via **typeclass** instances,\n- Can automatically **derive** `XmlWriter` typeclass if requested,\n- Invokes `toString()` as a **fallback** strategy when type is not supported directly or does not have an XmlWriter instance in scope.\n- Decouples data structure traversal (`XmlWriter`) from output assembly (`XmlOutputBuilder`)\n\n## Scala types supported directly without the need for typeclass derivation\n- **Case classes** and nested case classes (including recursive, deeply nested types)\n- **Enums and sealed trait hierarchies**\n- **Tuples**: e.g. `(A, B)`, `(A, B, C)` etc.\n- **Named tuples**: `(a: A, b: B)`\n- **Instances of `Selectable` with a `Fields` type**: serialization for structural types and objects extending `Selectable` with a `Fields` member type\n- **Opaque types with an upper bound**\n- **Iterable[T]** collections and **Array[T]**\n- **Option[T]**: (properly serializes presence or absence) \n- **Either[T]**\n- All standard **Scala primitive types**: `Int`, `Long`, `Double`, `Float`, `Boolean`, `Char`, `Short`, `Byte` and **`String`**\n- **Big number types**: `BigInt`, `BigDecimal`\n\n## Supported Java types\n- **Java boxed primitives:** `java.lang.Integer`, `java.lang.Long`, `java.lang.Double`, etc.\n- **Java records**\n- **Java enums**\n- **Java iterables:** support for `java.util.List`, `java.util.Set`, and other iterables\n- **Java maps:** support for `java.util.Map` and subclasses\n\n## Supported annotations\n\n- All annotations are defined in `org.encalmo.writer.xml.annotation`.\n- Annotations can be placed on types, fields, values and enum cases, on case class fields or sealed trait members.\n- Custom tag and attribute names are only required when you want to override defaults.\n\n| Annotation            | Description                                                                                           |\n|-----------------------|-------------------------------------------------------------------------------------------------------|\n| `@xmlAttribute`       | Marks the target to be serialized as an XML attribute of the enclosing element rather than as a child.   |\n| `@xmlContent`         | Marks target as the content (text value) of the XML element instead of a tag or attribute.           |\n| `@xmlTag`             | Sets a custom XML tag or attribute name for this target (overrides the target name in serialization).   |\n| `@xmlAdditionalTag` | Annotation to wrap value in and additional XML element |\n| `@xmlTagLabelAndType` | Annotation to mandate nested tag elements for a field: \u0026lt;field\u0026gt;\u0026lt;type\u0026gt; ... \u0026lt;/type\u0026gt;\u0026lt;/field\u0026gt; |\n| `@xmlItemTag`         | Annotation to define the name of the XML element wrapping each item in an array or collection. This will override custom names of the items in the collection.                              |\n| `@xmlAdditionalItemTag`         | Annotation to define the name of the XML element additionally wrapping each item in an array or collection. This will NOT override custom names of the items in the collection.                              |\n| `@xmlNoItemTags`      | Prevents wrapping each collection element in an extra XML tag; all items are added directly.          |\n| `@xmlValue`           | Defines a static value for an element, useful for enum cases     |\n| `@xmlValueSelector`   | Selects which member/field/property from a nested type is used as the value/text for this element.    |\n| `@xmlEnumCaseValuePlain`   | Annotation to force writing the enum case value as plain text, without wrapping it in a tag.   |\n\n## Key abstractions\n\n- object [`XmlWriter`](XmlWriter.scala) provides the main user-facing API, a host of methods to serialize data types to XML,\n- trait `XmlWriter[T]` defines typeclass interface,\n- trait [`XmlOutputBuilder`](XmlOutputBuilder.scala) defines low-level API for constructing XML output,\n- object `XmlOutputBuilder` provides a set of default implementations of `XmlOutputBuilder` trait producing indented or compact format, building a `String` or writing directly to the `java.io.OutputStream`\n\n## How do we tag elements?\n\n### Root element tag\n\nRoot tag can be either provided by the user or derived from the type name.\n```scala\ncase class Foo(bar: String)\nval entity = Foo(\"HELLO\")\n\n// \u003cFoo\u003e\u003cbar\u003eHELLO\u003c/bar\u003e\u003c/Foo\u003e\nval xml1 = XmlWriter.writeIndented(entity) \n\n// \u003cExample\u003e\u003cbar\u003eHELLO\u003c/bar\u003e\u003c/Example\u003e\nval xml2 = XmlWriter.writeIndentedUsingRootTagName(\"Example\", entity, addXmlDeclaration = false)\n```\n\n## Nested elements\n\nNested elements borrow tag name either from:\n- field name of case classes, selectables or records\n- enum case name or value\n- declared type name (including type aliases and opaque types)\n- keys of the map\n- @xmlTag and @xmlItemTag annotations\n\n```scala\ncase class Tool(name: String, weight: Double)\ncase class ToolBox(hammer: Tool, screwdriver: Tool)\nval entity =\n  ToolBox(\n    hammer = Tool(name = \"Hammer\", weight = 10.0),\n    screwdriver = Tool(name = \"Screwdriver\", weight = 2.0)\n  )\nval xml = XmlWriter.writeIndented(entity)\nprintln(xml)\n```\n```xml\n\u003c?xml version='1.0' encoding='UTF-8'?\u003e\n\u003cToolBox\u003e\n    \u003chammer\u003e\n        \u003cname\u003eHammer\u003c/name\u003e\n        \u003cweight\u003e10.0\u003c/weight\u003e\n    \u003c/hammer\u003e\n    \u003cscrewdriver\u003e\n        \u003cname\u003eScrewdriver\u003c/name\u003e\n        \u003cweight\u003e2.0\u003c/weight\u003e\n    \u003c/screwdriver\u003e\n\u003c/ToolBox\u003e\n```\n\n## Dependencies\n\n   - [Scala](https://www.scala-lang.org) \u003e= 3.7.4\n   - org.encalmo [**macro-utils** 0.11.0](https://central.sonatype.com/artifact/org.encalmo/macro-utils_3)\n\n## Usage\n\nUse with SBT\n\n    libraryDependencies += \"org.encalmo\" %% \"xmlwriter\" % \"0.13.0\"\n\nor with SCALA-CLI\n\n    //\u003e using dep org.encalmo::xmlwriter:0.13.0\n\n## More examples\n\nExample with nested case classes and optional fields:\n\n```scala\nimport org.encalmo.writer.xml.XmlWriter\n\ncase class Address(\n  street: String,\n  city: String,\n  postcode: String,\n  country: Option[String] = None\n)\n\ncase class Company(\n  name: String,\n  address: Address\n)\n\ncase class Employee(\n  name: String,\n  age: Int,\n  email: Option[String],\n  address: Option[Address],\n  company: Option[Company]\n)\n\nval employee = Employee(\n  name = \"Alice Smith\",\n  age = 29,\n  email = Some(\"alice.smith@company.com\"),\n  address = Some(\n    Address(\n      street = \"456 Market Ave\",\n      city = \"Metropolis\",\n      postcode = \"90210\",\n      country = None\n    )\n  ),\n  company = Some(\n    Company(\n      name = \"Acme Widgets Inc.\",\n      address = Address(\n        street = \"123 Corporate Plaza\",\n        city = \"Metropolis\",\n        postcode = \"90211\",\n        country = Some(\"USA\")\n      )\n    )\n  )\n)\n\n// Serialize as indented XML (with XML declaration)\nval xml: String = XmlWriter.writeIndented(employee)\nprintln(xml)\n```\nOutput:\n```xml\n\u003c?xml version='1.0' encoding='UTF-8'?\u003e\n\u003cEmployee\u003e\n    \u003cname\u003eAlice Smith\u003c/name\u003e\n    \u003cage\u003e29\u003c/age\u003e\n    \u003cemail\u003ealice.smith@company.com\u003c/email\u003e\n    \u003caddress\u003e\n        \u003cstreet\u003e456 Market Ave\u003c/street\u003e\n        \u003ccity\u003eMetropolis\u003c/city\u003e\n        \u003cpostcode\u003e90210\u003c/postcode\u003e\n    \u003c/address\u003e\n    \u003ccompany\u003e\n        \u003cname\u003eAcme Widgets Inc.\u003c/name\u003e\n        \u003caddress\u003e\n            \u003cstreet\u003e123 Corporate Plaza\u003c/street\u003e\n            \u003ccity\u003eMetropolis\u003c/city\u003e\n            \u003cpostcode\u003e90211\u003c/postcode\u003e\n            \u003ccountry\u003eUSA\u003c/country\u003e\n        \u003c/address\u003e\n    \u003c/company\u003e\n\u003c/Employee\u003e\n```\n\n```scala\n// Example: Serialize a case class with collections and XML annotations\n\nimport org.encalmo.writer.xml.XmlWriter\nimport org.encalmo.writer.xml.annotation.{xmlAttribute, xmlItemTag, xmlTag}\n\ncase class Tag(\n  @xmlAttribute name: String,\n  value: String\n)\n\n@xmlTag(\"Bookshelf\")\ncase class Library(\n  @xmlAttribute libraryId: String,\n  name: String,\n  @xmlItemTag(\"Book\") books: List[Book]\n)\n\ncase class Book(\n  @xmlAttribute isbn: String,\n  title: String,\n  author: String,\n  tags: List[Tag]\n)\n\nval library = Library(\n  libraryId = \"lib123\",\n  name = \"City Library\",\n  books = List(\n    Book(\n      isbn = \"978-3-16-148410-0\",\n      title = \"Programming Scala\",\n      author = \"Dean Wampler\",\n      tags = List(\n        Tag(name = \"Scala\", value = \"Functional\"),\n        Tag(name = \"Programming\", value = \"JVM\")\n      )\n    ),\n    Book(\n      isbn = \"978-1-61729-065-7\",\n      title = \"Functional Programming in Scala\",\n      author = \"Paul Chiusano\",\n      tags = List(\n        Tag(name = \"Scala\", value = \"FP\"),\n        Tag(name = \"Education\", value = \"Advanced\")\n      )\n    )\n  )\n)\n\nval xml: String = XmlWriter.writeIndented(library)\nprintln(xml)\n```\nOutput:\n```xml\n\u003c?xml version='1.0' encoding='UTF-8'?\u003e\n\u003cBookshelf libraryId=\"lib123\"\u003e\n    \u003cname\u003eCity Library\u003c/name\u003e\n    \u003cbooks\u003e\n        \u003cBook isbn=\"978-3-16-148410-0\"\u003e\n            \u003ctitle\u003eProgramming Scala\u003c/title\u003e\n            \u003cauthor\u003eDean Wampler\u003c/author\u003e\n            \u003ctags\u003e\n                \u003cTag name=\"Scala\"\u003eFunctional\u003c/Tag\u003e\n                \u003cTag name=\"Programming\"\u003eJVM\u003c/Tag\u003e\n            \u003c/tags\u003e\n        \u003c/Book\u003e\n        \u003cBook isbn=\"978-1-61729-065-7\"\u003e\n            \u003ctitle\u003eFunctional Programming in Scala\u003c/title\u003e\n            \u003cauthor\u003ePaul Chiusano\u003c/author\u003e\n            \u003ctags\u003e\n                \u003cTag name=\"Scala\"\u003eFP\u003c/Tag\u003e\n                \u003cTag name=\"Education\"\u003eAdvanced\u003c/Tag\u003e\n            \u003c/tags\u003e\n        \u003c/Book\u003e\n    \u003c/books\u003e\n\u003c/Bookshelf\u003e\n```\n\n\n## Project content\n\n```\n├── .github\n│   └── workflows\n│       ├── pages.yaml\n│       ├── release.yaml\n│       └── test.yaml\n│\n├── .gitignore\n├── .scalafmt.conf\n├── annotation.scala\n├── ExampleModel.test.scala\n├── ExampleModelSpec.test.scala\n├── LICENSE\n├── Order.java\n├── project.scala\n├── README.md\n├── SimpleTypeTreeVisitor.scala\n├── Status.java\n├── TagName.scala\n├── test.sh\n├── TestData.test.scala\n├── TestModel.test.scala\n├── TypeTreeIterator.scala\n├── TypeTreeVisitor.scala\n├── XmlOutputBuilder.scala\n├── XmlWriter.scala\n├── XmlWriterMacro.scala\n├── XmlWriterMacroVisitor.scala\n└── XmlWriterSpec.test.scala\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fencalmo%2Fxmlwriter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fencalmo%2Fxmlwriter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fencalmo%2Fxmlwriter/lists"}