{"id":25257531,"url":"https://github.com/michelin/avro-xml-mapper","last_synced_at":"2025-10-06T01:15:38.242Z","repository":{"id":197195288,"uuid":"648609043","full_name":"michelin/avro-xml-mapper","owner":"michelin","description":"Avro XML Mapper is a Java library that converts XML formatted data to Apache Avro format","archived":false,"fork":false,"pushed_at":"2025-01-07T17:04:06.000Z","size":197,"stargazers_count":15,"open_issues_count":5,"forks_count":6,"subscribers_count":8,"default_branch":"main","last_synced_at":"2025-01-07T17:53:48.477Z","etag":null,"topics":["avro","java","kafka","kafka-streams"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/michelin.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}},"created_at":"2023-06-02T11:22:54.000Z","updated_at":"2025-01-07T17:04:10.000Z","dependencies_parsed_at":null,"dependency_job_id":"c0274f22-e641-4e44-b4ad-3658cf152ee8","html_url":"https://github.com/michelin/avro-xml-mapper","commit_stats":null,"previous_names":["michelin/avro-xml-mapper"],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michelin%2Favro-xml-mapper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michelin%2Favro-xml-mapper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michelin%2Favro-xml-mapper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michelin%2Favro-xml-mapper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/michelin","download_url":"https://codeload.github.com/michelin/avro-xml-mapper/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238427535,"owners_count":19470842,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["avro","java","kafka","kafka-streams"],"created_at":"2025-02-12T06:49:01.717Z","updated_at":"2025-10-06T01:15:38.224Z","avatar_url":"https://github.com/michelin.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\r\n\r\n\u003cimg src=\".readme/logo.svg\" alt=\"Avro XML Mapper\"/\u003e\r\n\r\n# Avro XML Mapper\r\n\r\n[![GitHub Build](https://img.shields.io/github/actions/workflow/status/michelin/avro-xml-mapper/build.yml?branch=main\u0026logo=github\u0026style=for-the-badge)](https://img.shields.io/github/actions/workflow/status/michelin/avro-xml-mapper/build.yml)\r\n[![Maven Central](https://img.shields.io/maven-central/v/com.michelin/avro-xml-mapper?style=for-the-badge\u0026logo=apache-maven\u0026label=Maven%20Central)](https://central.sonatype.com/search?q=com.michelin.avro-xml-mapper\u0026sort=name)\r\n![Supported Java Versions](https://img.shields.io/badge/Java-17--21-blue.svg?style=for-the-badge\u0026logo=openjdk)\r\n[![GitHub Stars](https://img.shields.io/github/stars/michelin/avro-xml-mapper?logo=github\u0026style=for-the-badge)](https://github.com/michelin/avro-xml-mapper)\r\n[![SonarCloud Coverage](https://img.shields.io/sonar/coverage/michelin_avro-xml-mapper?logo=sonarcloud\u0026server=https%3A%2F%2Fsonarcloud.io\u0026style=for-the-badge)](https://sonarcloud.io/component_measures?id=michelin_avro-xml-mapper\u0026metric=coverage\u0026view=list)\r\n[![SonarCloud Tests](https://img.shields.io/sonar/tests/michelin_avro-xml-mapper/main?server=https%3A%2F%2Fsonarcloud.io\u0026style=for-the-badge\u0026logo=sonarcloud)](https://sonarcloud.io/component_measures?metric=tests\u0026view=list\u0026id=michelin_avro-xml-mapper)\r\n[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg?logo=apache\u0026style=for-the-badge)](https://opensource.org/licenses/Apache-2.0)\r\n\r\n[Getting Started](#getting-started) • [Usage](#usage)\r\n\r\nTurn XML into Avro and vice versa.\r\n\r\n\u003c/div\u003e\r\n\r\n## Table of Contents\r\n\r\n* [Getting Started](#getting-started)\r\n* [Usage](#usage)\r\n  * [XPath](#xpath)\r\n  * [Structure](#structure)\r\n    * [Single Element](#single-element)\r\n    * [List](#list)\r\n    * [Map](#map)\r\n  * [Logical Type](#logical-type)\r\n    * [Date](#date)\r\n    * [Big Decimal](#big-decimal)\r\n  * [XML Namespace](#xml-namespace)\r\n  * [Keywords](#keywords)\r\n    * [keepEmptyTag](#keepemptytag)\r\n  * [Custom Implementations](#custom-implementations)\r\n* [Contribution](#contribution)\r\n\r\n## Getting Started\r\n\r\nTo get started, add the following dependency:\r\n\r\n```xml\r\n\u003cdependency\u003e\r\n    \u003cgroupId\u003ecom.michelin\u003c/groupId\u003e\r\n    \u003cartifactId\u003eavro-xml-mapper\u003c/artifactId\u003e\r\n    \u003cversion\u003e${avro-xml-mapper.version}\u003c/version\u003e\r\n\u003c/dependency\u003e\r\n```\r\n\r\n## Usage\r\n\r\n### XPath\r\n\r\nThe XPath attribute is used to specify the path of the element in the XML file.\r\n\r\n### Structure\r\n\r\n#### Single Element\r\n\r\nA single element is represented as follows:\r\n\r\n\u003ctable style=\"width:100%\"\u003e\r\n\u003ctr\u003e\u003cth style=\"width:50%\"\u003eAVSC\u003c/th\u003e\u003cth style=\"width:50%\"\u003eXML\u003c/th\u003e\u003c/tr\u003e\r\n\u003ctd\u003e\r\n\r\n```avro schema\r\n{\r\n  \"name\": \"Object\",\r\n  \"type\": \"record\",\r\n  \"namespace\": \"com.example\",\r\n  \"xpath\": \"/objectRoot\",\r\n  \"fields\": [\r\n    {\"name\": \"element\", \"type\": \"string\", \"xpath\": \"element\"}\r\n  ]\r\n}\r\n```\r\n\r\n\u003c/td\u003e\r\n\u003ctd\u003e\r\n\r\n```xml\r\n\u003cobjectRoot\u003e\r\n    \u003celement\u003econtent\u003c/element\u003e\r\n\u003c/objectRoot\u003e\r\n```\r\n\r\n\u003c/table\u003e\r\n\r\n#### List\r\n\r\nLists can be applied to any repeating element in the XML file. The XPath attribute should point to the repeating element.\r\n\r\n\u003ctable style=\"width:100%\"\u003e\r\n\u003ctr\u003e\u003cth style=\"width:50%\"\u003eAVSC\u003c/th\u003e\u003cth style=\"width:50%\"\u003eXML\u003c/th\u003e\u003c/tr\u003e\r\n\u003ctd\u003e\r\n\r\n```avro schema\r\n{\r\n  \"name\": \"Object\",\r\n  \"type\": \"record\",\r\n  \"namespace\": \"com.example\",\r\n  \"xpath\": \"/objectRoot\",\r\n  \"fields\": [\r\n    {\r\n      \"name\": \"stringList\",\r\n      \"xpath\": \"child\",\r\n      \"type\": {\"type\": \"array\", \"items\": \"string\"},\r\n      \"default\": {}\r\n    }\r\n  ]\r\n}\r\n```\r\n\r\n\u003c/td\u003e\r\n\u003ctd\u003e\r\n\r\n```xml\r\n\u003cobjectRoot\u003e\r\n    \u003cchild\u003econtent1\u003c/child\u003e\r\n    \u003cchild\u003econtent2\u003c/child\u003e\r\n\u003c/objectRoot\u003e \r\n```\r\n\r\n\u003c/table\u003e\r\n\r\nComplex types can also be defined as follows:\r\n\r\n\u003ctable style=\"width:100%\"\u003e\r\n\u003ctr\u003e\u003cth style=\"width:50%\"\u003eAVSC\u003c/th\u003e\u003cth style=\"width:50%\"\u003eXML\u003c/th\u003e\u003c/tr\u003e\r\n\u003ctd\u003e\r\n\r\n```avro schema\r\n{\r\n  \"name\": \"Object\",\r\n  \"type\": \"record\",\r\n  \"namespace\": \"com.example\",\r\n  \"xpath\": \"/objectRoot\",\r\n  \"fields\": [\r\n    {\r\n      \"name\": \"recordList\",\r\n      \"xpath\": \"recordList/listItem\",\r\n      \"type\": {\r\n        \"type\": \"array\",\r\n        \"items\": {\r\n          \"type\": \"record\",\r\n          \"name\": \"SubXMLTestModelMultipleXpath\",\r\n          \"fields\": [\r\n            {\"name\": \"subStringField\", \"type\" : [\"null\",\"string\"], \"default\": null, \"customXpath1\": \"subStringField\", \"customXpath2\": \"altSubStringField\"},\r\n            {\"name\": \"subIntField\", \"type\" : [\"null\",\"int\"], \"default\": null, \"customXpath1\": \"subIntField\", \"customXpath2\": \"altSubIntField\"},\r\n            {\"name\": \"subStringFieldFromAttribute\", \"type\" : [\"null\",\"string\"], \"default\": null, \"customXpath1\": \"subIntField/@attribute\", \"customXpath2\": \"altSubIntField/@attribute\"}\r\n          ],\r\n          \"default\": {}\r\n        }\r\n      },\r\n      \"default\": []\r\n    }\r\n  ]\r\n}\r\n```\r\n\r\n\u003c/td\u003e\r\n\u003ctd\u003e\r\n\r\n```xml\r\n\u003cobjectRoot\u003e\r\n    \u003crecordList\u003e\r\n        \u003clistItem\u003e\r\n            \u003csubStringField\u003eitem1\u003c/subStringField\u003e\r\n            \u003csubIntField attribute=\"attribute1\"\u003e1\u003c/subIntField\u003e\r\n        \u003c/listItem\u003e\r\n        \u003clistItem\u003e\r\n            \u003csubStringField\u003eitem2\u003c/subStringField\u003e\r\n            \u003csubIntField attribute=\"attribute2\"\u003e2\u003c/subIntField\u003e\r\n        \u003c/listItem\u003e\r\n        \u003clistItem\u003e\r\n            \u003csubStringField\u003eitem3\u003c/subStringField\u003e\r\n            \u003csubIntField attribute=\"attribute3\"\u003e3\u003c/subIntField\u003e\r\n        \u003c/listItem\u003e\r\n    \u003c/recordList\u003e\r\n\u003c/objectRoot\u003e\r\n```\r\n\r\n\u003c/table\u003e\r\n\r\n#### Map\r\n\r\nMaps have two accepted formats:\r\n- A list of elements with a key attribute\r\n\r\n\u003ctable style=\"width:100%\"\u003e\r\n\u003ctr\u003e\u003cth style=\"width:50%\"\u003eAVSC\u003c/th\u003e\u003cth style=\"width:50%\"\u003eXML\u003c/th\u003e\u003c/tr\u003e\r\n\u003ctd\u003e\r\n\r\n```avro schema\r\n{\r\n  \"name\": \"Object\",\r\n  \"type\": \"record\",\r\n  \"namespace\": \"com.example\",\r\n  \"xpath\": \"/objectRoot\",\r\n  \"fields\": [\r\n    {\r\n      \"name\": \"stringMapFormat1\",\r\n      \"xpath\": { \"rootXpath\": \"element\", \"keyXpath\": \"@key\", \"valueXpath\": \".\" },\r\n      \"type\": { \"type\": \"map\", \"values\": \"string\" },\r\n      \"default\": {}\r\n    }\r\n  ]\r\n}\r\n```\r\n\r\n\u003c/td\u003e\r\n\u003ctd\u003e\r\n\r\n```xml\r\n\u003cobjectRoot\u003e\r\n    \u003celement key=\"key1\"\u003econtent1\u003c/element\u003e\r\n    \u003celement key=\"key2\"\u003econtent2\u003c/element\u003e\r\n\u003c/objectRoot\u003e \r\n```\r\n\r\n\u003c/table\u003e\r\n\r\n- A list of nodes with a key element and a value element\r\n\r\n\u003ctable style=\"width:100%\"\u003e\r\n\u003ctr\u003e\u003cth style=\"width:50%\"\u003eAVSC\u003c/th\u003e\u003cth style=\"width:50%\"\u003eXML\u003c/th\u003e\u003c/tr\u003e\r\n\u003ctd\u003e\r\n\r\n```avro schema\r\n{\r\n  \"name\": \"Object\",\r\n  \"type\": \"record\",\r\n  \"namespace\": \"com.example\",\r\n  \"xpath\": \"/objectRoot\",\r\n  \"fields\": [\r\n    {\r\n      \"name\": \"stringMapFormat2\",\r\n      \"xpath\": { \"rootXpath\": \"element\", \"keyXpath\": \"key\", \"valueXpath\": \"value\" },\r\n      \"type\": { \"type\": \"map\", \"values\": \"string\" },\r\n      \"default\": {}\r\n    }\r\n  ]\r\n}\r\n```\r\n\r\n\u003c/td\u003e\r\n\u003ctd\u003e\r\n\r\n```xml\r\n\u003cobjectRoot\u003e\r\n    \u003celement\u003e\r\n        \u003ckey\u003ekey1\u003c/key\u003e\r\n        \u003cvalue\u003econtent1\u003c/value\u003e\r\n    \u003c/element\u003e\r\n    \u003celement\u003e\r\n        \u003ckey\u003ekey2\u003c/key\u003e\r\n        \u003cvalue\u003econtent2\u003c/value\u003e\r\n    \u003c/element\u003e\r\n\u003c/objectRoot\u003e\r\n```\r\n\r\n\u003c/table\u003e\r\n\r\nIn both cases, the `rootXpath` attribute always points to the repeating element of the list.\r\n\r\n### Logical Type\r\n\r\n#### Date\r\n\r\nOnly the `timestamp-millis` Long logical type is handled and has multiple accepted formats:\r\n\r\n- ISO8601 date-time\r\n- ISO8601 date\r\n- Flat date (yyyyMMddz) which gets the UTC 12:00:00.000 time to avoid timezone issues\r\n- Flat date-time (yyyyMMddHHmmssz) which gets the UTC timezone assigned\r\n- ISO8601 date-time without offset\r\n- ISO8601 date without offset\r\n- Flat date without offset (yyyyMMdd) which gets the UTC 12:00:00.000 time to avoid timezone issues\r\n- Flat date-time without offset (yyyyMMdd HHmmss) which gets the UTC timezone assigned\r\n- Flat date-time without offset and without timezone (yyyy-MM-dd HH:mm:ss) which gets the UTC timezone assigned\r\n- Flat date-time with offset (yyyy-MM-dd'T'HH:mm:ss'T'00:00)\r\n\r\nThey are all converted to the `Instant` Java type.\r\n\r\n#### Big Decimal\r\n\r\nOnly the `Decimal` byte logical type is handled. It is converted to a `BigDecimal` Java type.\r\n\r\n### XML Namespace\r\n\r\nThe `xmlNamespaces` attribute defined at the root of the AVSC file is used to specify the namespaces used in the XML file.\r\n\r\n\u003e It should be noted that this attribute is used in different ways depending on the conversion direction as described in the following sections.\r\n\r\n#### XML to Avro \r\n\r\nThe namespaces are used to unify the XML file. \r\nIf multiple namespace definitions refer to the same URI, only the one defined in the `xmlNamespaces` attribute will be kept during conversion.\r\n\r\nFor instance, with the given AVSC and XML: \r\n\r\n```avro schema\r\n{\r\n  \"name\": \"Object\",\r\n  \"type\": \"record\",\r\n  \"namespace\": \"com.example\",\r\n  \"xpath\": \"objectRoot\",\r\n  \"xmlNamespaces\": {\r\n    \"null\": \"http://namespace.uri/default\",\r\n    \"ns1\": \"http://namespace.uri/1\"\r\n  },\r\n  \"fields\": [\r\n    {\"name\": \"element\", \"type\": \"string\", \"xpath\": \"element\"},\r\n    {\"name\": \"secondElement\", \"type\": \"string\", \"xpath\": \"ns1:secondElement\"},\r\n    {\"name\": \"thirdElement\", \"type\": \"string\", \"xpath\": \"ns1:thirdElement\"}\r\n  ]\r\n}\r\n```\r\n\r\n```xml \r\n\u003cobjectRoot xmlns=\"http://namespace.uri/default\"\r\n            xmlns:ns1=\"http://namespace.uri/1\"\u003e\r\n    \u003celement\u003econtent\u003c/element\u003e\r\n    \u003cns1:secondElement\u003esecond element content\u003c/ns1:secondElement\u003e\r\n    \u003cns2:thirdElement xmlns:ns2=\"http://namespace.uri/1\"\u003ethird element content\u003c/ns2:thirdElement\u003e\r\n\u003c/objectRoot\u003e\r\n```\r\n\r\nBefore conversion to Avro, the initial document is tweaked as such:\r\n\r\n```xml\r\n\u003cnoprefixns:objectRoot xmlns:noprefixns=\"http://namespace.uri/default\"\r\n            xmlns:ns1=\"http://namespace.uri/1\"\u003e\r\n    \u003cnoprefixns:element\u003econtent\u003c/noprefixns:element\u003e\r\n    \u003cns1:secondElement\u003esecond element content\u003c/ns1:secondElement\u003e\r\n    \u003cns1:thirdElement\u003ethird element content\u003c/ns1:thirdElement\u003e\r\n\u003c/noprefixns:objectRoot\u003e\r\n```\r\n\r\nThe root `xmlns` namespace is replaced with `xmlns:noprefixns` and the `ns1` is simply preserved.\r\n\r\nThe `ns2` namespace is removed because it refers to the same URI as the `ns1` namespace.\r\n\r\n\u003e Failing to provide `xmlNamespaces` for XML to Avro conversion simply means that namespaces in XPath have to be consistent.\r\n\r\n#### Avro to XML\r\n\r\nThe namespaces are used for root namespace definition.\r\n\r\n\u003e Failing to provide `xmlNamespaces` for Avro to XML conversion means that no namespace should be used in the XPath attributes, as it would mean that the produced XML would be invalid.\r\n\r\n## Keywords\r\n\r\n### keepEmptyTag\r\n\r\nThe `keepEmptyTag` attribute can be used to signify that the tag needs to be kept in the Avro to XML conversion in case the original Avro field is null:\r\n\r\n\u003ctable style=\"width:100%\"\u003e\r\n\u003ctr\u003e\u003cth style=\"width:50%\"\u003eAVSC\u003c/th\u003e\u003cth style=\"width:50%\"\u003eXML\u003c/th\u003e\u003c/tr\u003e\r\n\u003ctd\u003e\r\n\r\n```avro schema\r\n{\r\n  \"name\": \"Object\",\r\n  \"type\": \"record\",\r\n  \"namespace\": \"com.example\",\r\n  \"xpath\": \"/objectRoot\",\r\n  \"fields\": [\r\n    {\r\n      \"name\": \"emptyElement\",\r\n      \"xpath\": \"element\",\r\n      \"keepEmptyTag\": true,\r\n      \"type\": [\"null\",\"string\"],\r\n      \"default\": null\r\n    }\r\n  ]\r\n}\r\n```\r\n\r\n\u003c/td\u003e\r\n\r\n\u003ctd\u003e\r\n\r\n```xml\r\n\u003cobjectRoot\u003e\r\n    \u003celement /\u003e\r\n\u003c/objectRoot\u003e\r\n```\r\n\r\n\u003c/td\u003e\r\n\u003c/table\u003e\r\n\r\n### Custom Implementations\r\n\r\nUsing the provided method `AvroToXmlMapper#convertAvroToXmlDocument` allows for custom implementations and editing of the document before it is converted to String.\r\n\r\nConversion can be finalized using `GenericUtils#documentToString` method.\r\n\r\n## Contribution\r\n\r\nWe welcome contributions from the community! Before you get started, please take a look at\r\nour [contribution guide](https://github.com/michelin/avro-xml-mapper/blob/main/CONTRIBUTING.md) to learn about our guidelines\r\n\r\nand best practices. We appreciate your help in making Avro XML Mapper a better tool for everyone.\r\n\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmichelin%2Favro-xml-mapper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmichelin%2Favro-xml-mapper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmichelin%2Favro-xml-mapper/lists"}