xml-class-transformer
v2.3.0
Published
Fluently parse XML into beautiful JS/TS classes and serialize them. GoLang's encoding/xml alternative for JS/TS world.
Downloads
248
Readme
xml-class-transformer
xml-class-transformer
is a library, that lets you define XML elements as regular TypeScript classes, and then parse XML into these classes and marshalize them. The whole library is heavily inspirated by GoLang's encoding/xml
.
Installation
npm install xml-class-transformer --save
# For Yarn, use the command below.
yarn add xml-class-transformer
Quick example
@XmlElem({ name: 'Article' })
class Article {
@XmlChildElem({ type: () => String, name: 'Title' })
title: string;
@XmlChildElem({ type: () => String, name: 'Content' })
content: string;
constructor(d?: Article) {
Object.assign(this, d || {});
}
}
const parsedArticle: Article = xmlToClass(
`<Article><Title>Some title</Title><Content>The content of the article.</Content></Article>`,
Article,
);
console.log(parsedArticle); // Article { title: 'Some title', content: 'The content of the article.' }
Features
- Declarative and easy TypeScript decorators.
- Union types (
@XmlChildElem({ union: () => [Employee, Manager] }) user: Employee | Manager;
). - XML Arrays, including arrays with union types (e.g.
@XmlChildElem({ type: () => [Employee, Manager], array: true }) users: (Employee | Manager)[]
). - XML Attributes.
- XML Declarations (
<?xml version="1.0" encoding="UTF-8"?>
). - XML Comments
- Battle-tested in production and unit coverage "> 80%".
- Complex and nested structures.
- Transformation and validation (with
class-transformer
andclass-validator
).
Upcoming features
These are features for more uncommon usage, most projects will not need them, but I might add support for them in the future.
- CDATA Support
- XML Namespaces
- Custom ordering
- Multiple chardata entries with the support for specified ordering.
- Custom parsers/serializers
- CLI tool for automatically generating class declarations out of an XML input. Something similar to what does miku/zek for GoLang.
Table of Contents
- Why?
- Parsing XML to class
- Serializing class to XML
- Examples
- Details
- Changelog
- Installation from CDN
- API Documentation
- Development
- License
Why?
The need for a library like this was huge for one of the projects with an XML API I was working for. For a huge time i was searching for a beautiful was to represent data in XML, parse them, validate, without dealing with does hairy and messed up XML parsers. I was drooling over the GoLang's encoding/xml
implementation with their struct tags, and came up with this idea of using classes with decorators.
Huge advantage of this approach is that you can also use class-validator
and class-transformer
, which gives you almost no limits to validation.
The library is still on it's very early stage, but we already use it in production, so don't worry to experiment with it and file an issue or pull request if you want.
Usage
Lets define our XML schema in the form of classes:
@XmlElem({ name: 'article' })
class Article {
@XmlChildElem({ type: () => String })
title: string;
@XmlChildElem({ type: () => String, array: true })
authors: string[];
@XmlChildElem({ type: () => Review, array: true })
reviews: Review[];
@XmlComments()
xmlComments: string[];
constructor(article?: Article) {
Object.assign(this, article || {});
}
}
@XmlElem({ name: 'review' })
class Review {
@XmlAttribute({ name: 'language', type: () => String })
lang: string;
@XmlAttribute({ name: 'date', type: () => String })
date: string;
@XmlAttribute({ name: 'author-id', type: () => Number })
authorId: number;
@XmlChardata({ type: () => String })
text: string;
constructor(review?: Review) {
Object.assign(this, review || {});
}
}
The above class represents an XML element like this:
<article>
<title>Article 1</title>
<authors>Tom</authors>
<authors>Bob</authors>
<reviews language="en" date="2020-01-01" author-id="1">contents text</reviews>
<reviews language="en" date="2020-01-01" author-id="2">contents text</reviews>
<!--some comment-->
<!--some other comment-->
</article>
Parsing XML to class
import {
XmlElem,
XmlChildElem,
XmlComments,
classToXml,
xmlToClass,
} from './xml-to-class-transformer';
@XmlElem({ name: 'Article' })
class Article {
@XmlChildElem({ type: () => String, name: 'Title' })
title: string;
@XmlComments()
comments: string[];
constructor(d?: Article) {
Object.assign(this, d || {});
}
}
const xml = `
<?xml version="1.0" encoding="UTF-8"?>
<Article>
<Title>Article 1</Title>
<Content>content 1</Content>
</Article>
`;
const parsedArticle: Article = xmlToClass(xml, Article);
console.log(parsedArticle);
// Output:
// Article { title: 'Article 1', content: 'content 1' }
Serializing class to XML
const serialized = classToXml(
new Article({
title: 'Article 2',
content: 'content 2',
}),
);
console.log(serialized);
// Output:
// <?xml version="1.0" encoding="UTF-8"?>
// <Article>
// <Title>Article 2</Title>
// <Content>content 2</Content>
// </Article>
Examples
Take a look at the examples.
Details
XML is inherently not very programming-language friendly. It does not follow the common "structured" key-value approach of storing data. Because of that library developers like myself have to find some common ground between them. Generally details and pitfalls described here will not be needed to know of in ordinary projects with not too complex XML schemas. But in case if you have to thoroughly handle null
s and undefined
s then here you go ;)
null and undefined handling for primitives
When serializing classes to xml all properties with undefined
value will be excluded from the resulting xml. This is an intentional behaviour, and also in convenience with the behaviour of the JSON.stringify
which also omits undefined values. When serializing such XMLs with omitted tags back to classes, those omitted fields will have the same undefined
value. So in general undefined
values are straightforward to work with.
On the other hand serializing null
is a bit tricky: XMLs don't have such thing as null values. So we have to take some workaround: nulls for primitive types (string, number, boolean) will be serialized to empty chardata. For example this class:
class XmlNullProp {
@XmlChildElem({ type: () => Number })
nullProp: number | null;
constructor(d?: XmlNullProp) {
Object.assign(this, d || {});
}
}
console.log(classToXml({ nullProp: null }));
will be serialized to:
<?xml version="1.0" encoding="UTF-8"?><XmlNullProp/>
Same thing goes not only for numbers, but also for booleans and strings. When serilizing back, does XML tags with empty chardatas will be converted to properties with null values. But not for strings: strings are exception in the case of null handling: when converted from XML back to Classes null strings will be converted into empty strings. In general this is an acceptable behavior, because there is not really much of a choice.
null and undefined handling for objects
For objects handling of nulls and undefined values are a bit different too: undefined for object types will be preserved when converted back to classes. However the situation with null objects is different: because of no way to serialize null objects, null objects will become undefined
when converted back to classes.
null and undefined handling for arrays
For arrays nulls and undefined
s will become empty arrays. This is because XML inherently has no way to represent arrays, the closest functionality to that it can provide is to store multiple tags with the same name.
Changelog
All the changelog is in the CHANGELOG.md file
Installation from CDN
This module has an UMD bundle available through JSDelivr and Unpkg CDNs.
<!-- For UNPKG use the code below. -->
<script src="https://unpkg.com/xml-class-transformer"></script>
<!-- For JSDelivr use the code below. -->
<script src="https://cdn.jsdelivr.net/npm/xml-class-transformer"></script>
<script>
// UMD module is exposed through the "xml-class-transformer" global variable.
console.log(window['xml-class-transformer']);
</script>
API Documentation
Documentation generated from source files by Typedoc.
Development
There are predefined scripts for convenience:
npm version-major
- increments major version by 1, builds the projects, and makes a git commit. So1.0.0
becomes2.0.0
;npm version-premajor
- adds 1 to major version, and adds a-alpha.0
postfix, builds, and makes a git commit. Meant to be used for starting alpha releases for the next coming-up major version. So1.0.0
becomes2.0.0-alpha.0
npm version-prerelease
- adds 1 to the-alpha.0
part (called preid). Meant to be used afternpm version-premajor
for further fixes to the alpha version of the next coming-up major release. So2.0.0-alpha.0
becomes2.0.0-alpha.1
License
Released under MIT License.