Skip to content

Converter - Core Concepts

This guide covers the fundamental concepts you need to understand to use the StAX-XML converter effectively.

The x object is the primary interface for creating XML schemas. It provides factory methods for all schema types:

import { x } from 'stax-xml/converter';
// Create schemas
const stringSchema = x.string();
const numberSchema = x.number();
const objectSchema = x.object({...});
const arraySchema = x.array(elementSchema, xpath);

All schema methods return new schema instances, making the API immutable and chainable:

const schema = x.number()
.xpath('//price')
.min(0)
.max(1000);
// Each method returns a new schema
const baseNumber = x.number();
const withXPath = baseNumber.xpath('//value'); // New instance
const withMin = withXPath.min(0); // New instance
// Original remains unchanged
console.log(baseNumber.options.xpath); // undefined
console.log(withXPath.options.xpath); // '//value'

This immutability ensures schemas are reusable and composable:

// Base price schema
const priceSchema = x.number().min(0);
// Reuse with different XPath
const retailPrice = priceSchema.xpath('//retailPrice');
const wholesalePrice = priceSchema.xpath('//wholesalePrice');

The converter provides multiple parsing methods for different use cases:

Use parseSync() for synchronous, blocking parsing:

const schema = x.string().xpath('//title');
const result = schema.parseSync(xmlString);
// Returns: string

When to use:

  • XML is already in memory as a string
  • You’re in a synchronous context
  • Performance is critical (slightly faster than async)

Use parse() for asynchronous parsing:

const schema = x.object({
name: x.string().xpath('//name'),
value: x.number().xpath('//value')
});
const result = await schema.parse(xmlString);
// Returns: Promise<{ name: string; value: number; }>

When to use:

  • Parsing large XML documents
  • You’re already in an async context
  • You want non-blocking execution

Safe parsing methods return a result object instead of throwing errors:

const schema = x.number().xpath('//age').min(0).max(120);
// Safe sync parsing
const result = schema.safeParseSync('<age>150</age>');
if (result.success) {
console.log(result.data); // number
} else {
console.log(result.issues); // Array of error objects
}
// Safe async parsing
const result = await schema.safeParse('<age>150</age>');

ParseResult type:

type ParseResult<T> =
| { success: true; data: T }
| { success: false; issues: Array<{ message: string; path?: string }> };

When to use:

  • You want to handle errors explicitly
  • You’re validating user input
  • You don’t want to use try/catch blocks

The converter provides automatic TypeScript type inference using the Infer utility type:

import { x, type Infer } from 'stax-xml/converter';
const userSchema = x.object({
id: x.number().xpath('//id'),
username: x.string().xpath('//username'),
email: x.string().xpath('//email'),
active: x.string().xpath('//active').transform(v => v === 'true')
});
// Automatically infer the type
type User = Infer<typeof userSchema>;
// {
// id: number;
// username: string;
// email: string;
// active: boolean; // Note: transformed type!
// }
// Use the inferred type
const users: User[] = [];

Type inference works with nested structures and transformations:

const productSchema = x.object({
name: x.string().xpath('//name'),
price: x.number().xpath('//price'),
tags: x.array(x.string(), '//tag'),
specs: x.object({
weight: x.number().xpath('./weight'),
dimensions: x.string().xpath('./dimensions')
}).xpath('//specs').optional()
});
type Product = Infer<typeof productSchema>;
// {
// name: string;
// price: number;
// tags: string[];
// specs: { weight: number; dimensions: string; } | undefined;
// }
  1. Define schemas as const:

    const schema = x.object({...}) as const;
  2. Extract types early:

    type MyData = Infer<typeof mySchema>;
  3. Use in function signatures:

    function processData(data: Infer<typeof schema>) {
    // TypeScript knows the exact shape
    }

XPath is the primary method for selecting elements in XML documents.

// Absolute path from root
x.string().xpath('/root/element/child')
// Descendant search (anywhere in document)
x.string().xpath('//element')
// Attribute access
x.string().xpath('/@id')
x.string().xpath('//@href')
// Combined
x.string().xpath('/root/item/@name')

String and Number schemas:

const title = x.string().xpath('/book/title');
const price = x.number().xpath('/book/price');

Object schemas:

// XPath on individual fields
const book = x.object({
title: x.string().xpath('/book/title'),
author: x.string().xpath('/book/author')
});
// XPath on entire object (scopes child XPaths)
const book = x.object({
title: x.string().xpath('./title'), // Relative to /book
author: x.string().xpath('./author') // Relative to /book
}).xpath('/book');

Array schemas:

// XPath is REQUIRED for arrays
const items = x.array(
x.string(),
'//item' // Selects all <item> elements
);
// Array of objects
const books = x.array(
x.object({
title: x.string().xpath('./title'),
price: x.number().xpath('./price')
}),
'//book' // Each book becomes an object
);

Use predicates to filter elements:

// By attribute value
x.array(
x.object({...}),
'//book[@category="fiction"]'
)
// By position
x.string().xpath('//item[1]') // First item
// By element content
x.array(
x.object({...}),
'//product[@available="true"]'
)

The converter provides detailed error information when parsing fails:

import { XmlParseError } from 'stax-xml/converter';
try {
const result = schema.parseSync(invalidXml);
} catch (error) {
if (error instanceof XmlParseError) {
console.log(error.message); // Human-readable message
console.log(error.issues); // Array of specific issues
}
}

Common error scenarios:

1. Validation Errors

const schema = x.number().xpath('//age').min(18);
schema.parseSync('<age>15</age>');
// Error: Value 15 is less than minimum 18

2. Type Conversion Errors

const schema = x.number().xpath('//count');
schema.parseSync('<count>abc</count>');
// Error: Expected number, got NaN

3. Missing Required Fields

const schema = x.object({
required: x.string().xpath('//required')
});
schema.parseSync('<root></root>');
// Returns: { required: '' } (empty string, not error)

4. XPath Errors

const schema = x.array(x.string()); // Missing xpath
schema.parseSync('<root></root>');
// Error: Array schema requires xpath

1. Use Optional

const schema = x.object({
id: x.number().xpath('//id'),
optional: x.string().xpath('//optional').optional()
});
// Returns: { id: number; optional: string | undefined }

2. Use Safe Parsing

const result = schema.safeParseSync(xml);
if (!result.success) {
// Handle errors gracefully
return defaultValue;
}
return result.data;

3. Use Transform for Defaults

const schema = x.string()
.xpath('//value')
.transform(v => v || 'default');

Schemas can be composed and reused:

// Define reusable schemas
const priceSchema = x.number().min(0);
const idSchema = x.number().int().min(1);
// Compose in objects
const productSchema = x.object({
id: idSchema.xpath('//id'),
price: priceSchema.xpath('//price'),
salePrice: priceSchema.xpath('//salePrice').optional()
});
const addressSchema = x.object({
street: x.string().xpath('./street'),
city: x.string().xpath('./city'),
zip: x.string().xpath('./zip')
}).xpath('/address');
const personSchema = x.object({
name: x.string().xpath('/person/name'),
address: addressSchema // Nested object schema
});
const tagSchema = x.string();
const tagsArray = x.array(tagSchema, '//tag');
const articleSchema = x.object({
title: x.string().xpath('//title'),
tags: tagsArray
});
// Synchronous - slightly faster for small documents
const result = schema.parseSync(smallXml);
// Asynchronous - better for large documents
const result = await schema.parse(largeXml);
// ❌ Slow - searches entire document multiple times
const schema = x.object({
a: x.string().xpath('//a'),
b: x.string().xpath('//b'),
c: x.string().xpath('//c')
});
// ✅ Better - single root XPath, relative children
const schema = x.object({
a: x.string().xpath('./a'),
b: x.string().xpath('./b'),
c: x.string().xpath('./c')
}).xpath('/root');
// ❌ Creates new schema every time
function parseUser(xml: string) {
const schema = x.object({...});
return schema.parseSync(xml);
}
// ✅ Define schema once, reuse
const userSchema = x.object({...});
function parseUser(xml: string) {
return userSchema.parseSync(xml);
}