Converter - Core Concepts
This guide covers the fundamental concepts you need to understand to use the StAX-XML converter effectively.
Schema Builder (x)
Section titled “Schema Builder (x)”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 schemasconst stringSchema = x.string();const numberSchema = x.number();const objectSchema = x.object({...});const arraySchema = x.array(elementSchema, xpath);Fluent API Pattern
Section titled “Fluent API Pattern”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 schemaconst baseNumber = x.number();const withXPath = baseNumber.xpath('//value'); // New instanceconst withMin = withXPath.min(0); // New instance
// Original remains unchangedconsole.log(baseNumber.options.xpath); // undefinedconsole.log(withXPath.options.xpath); // '//value'This immutability ensures schemas are reusable and composable:
// Base price schemaconst priceSchema = x.number().min(0);
// Reuse with different XPathconst retailPrice = priceSchema.xpath('//retailPrice');const wholesalePrice = priceSchema.xpath('//wholesalePrice');Parsing Modes
Section titled “Parsing Modes”The converter provides multiple parsing methods for different use cases:
Synchronous Parsing
Section titled “Synchronous Parsing”Use parseSync() for synchronous, blocking parsing:
const schema = x.string().xpath('//title');const result = schema.parseSync(xmlString);// Returns: stringWhen to use:
- XML is already in memory as a string
- You’re in a synchronous context
- Performance is critical (slightly faster than async)
Asynchronous Parsing
Section titled “Asynchronous Parsing”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
Section titled “Safe Parsing”Safe parsing methods return a result object instead of throwing errors:
const schema = x.number().xpath('//age').min(0).max(120);
// Safe sync parsingconst 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 parsingconst 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
Type Inference
Section titled “Type Inference”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 typetype User = Infer<typeof userSchema>;// {// id: number;// username: string;// email: string;// active: boolean; // Note: transformed type!// }
// Use the inferred typeconst users: User[] = [];Complex Type Inference
Section titled “Complex Type Inference”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;// }Type Inference Best Practices
Section titled “Type Inference Best Practices”-
Define schemas as const:
const schema = x.object({...}) as const; -
Extract types early:
type MyData = Infer<typeof mySchema>; -
Use in function signatures:
function processData(data: Infer<typeof schema>) {// TypeScript knows the exact shape}
XPath Integration
Section titled “XPath Integration”XPath is the primary method for selecting elements in XML documents.
XPath Basics
Section titled “XPath Basics”// Absolute path from rootx.string().xpath('/root/element/child')
// Descendant search (anywhere in document)x.string().xpath('//element')
// Attribute accessx.string().xpath('/@id')x.string().xpath('//@href')
// Combinedx.string().xpath('/root/item/@name')XPath in Different Schemas
Section titled “XPath in Different Schemas”String and Number schemas:
const title = x.string().xpath('/book/title');const price = x.number().xpath('/book/price');Object schemas:
// XPath on individual fieldsconst 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 arraysconst items = x.array( x.string(), '//item' // Selects all <item> elements);
// Array of objectsconst books = x.array( x.object({ title: x.string().xpath('./title'), price: x.number().xpath('./price') }), '//book' // Each book becomes an object);XPath Predicates
Section titled “XPath Predicates”Use predicates to filter elements:
// By attribute valuex.array( x.object({...}), '//book[@category="fiction"]')
// By positionx.string().xpath('//item[1]') // First item
// By element contentx.array( x.object({...}), '//product[@available="true"]')Error Handling
Section titled “Error Handling”The converter provides detailed error information when parsing fails:
XmlParseError
Section titled “XmlParseError”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 }}Error Types
Section titled “Error Types”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 182. Type Conversion Errors
const schema = x.number().xpath('//count');schema.parseSync('<count>abc</count>');// Error: Expected number, got NaN3. 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 xpathschema.parseSync('<root></root>');// Error: Array schema requires xpathError Recovery Strategies
Section titled “Error Recovery Strategies”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');Schema Composition
Section titled “Schema Composition”Schemas can be composed and reused:
Reusing Schemas
Section titled “Reusing Schemas”// Define reusable schemasconst priceSchema = x.number().min(0);const idSchema = x.number().int().min(1);
// Compose in objectsconst productSchema = x.object({ id: idSchema.xpath('//id'), price: priceSchema.xpath('//price'), salePrice: priceSchema.xpath('//salePrice').optional()});Nested Objects
Section titled “Nested Objects”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});Schema Arrays
Section titled “Schema Arrays”const tagSchema = x.string();const tagsArray = x.array(tagSchema, '//tag');
const articleSchema = x.object({ title: x.string().xpath('//title'), tags: tagsArray});Performance Considerations
Section titled “Performance Considerations”Synchronous vs Asynchronous
Section titled “Synchronous vs Asynchronous”// Synchronous - slightly faster for small documentsconst result = schema.parseSync(smallXml);
// Asynchronous - better for large documentsconst result = await schema.parse(largeXml);XPath Optimization
Section titled “XPath Optimization”// ❌ Slow - searches entire document multiple timesconst schema = x.object({ a: x.string().xpath('//a'), b: x.string().xpath('//b'), c: x.string().xpath('//c')});
// ✅ Better - single root XPath, relative childrenconst schema = x.object({ a: x.string().xpath('./a'), b: x.string().xpath('./b'), c: x.string().xpath('./c')}).xpath('/root');Compiled Schemas
Section titled “Compiled Schemas”Use compile() when you parse many documents with the same schema:
const personSchema = x.object({ id: x.number().xpath('./@id').int(), name: x.string().xpath('./name'), age: x.number().xpath('./age').int(), birthday: x.string().xpath('./birthday'), married: x.string() .xpath('./married') .transform(value => value === 'true'), firstTime: x.string().xpath('./married/@firstTime'), nickname: x.string().xpath('./nickname').optional(), address: x.object({ city: x.string().xpath('./city'), zip: x.string().xpath('./zip/text()') }).xpath('./address'), aliases: x.array(x.string(), './alias'), contacts: x.array( x.object({ type: x.string().xpath('./@type'), value: x.string().xpath('./value/text()') }), './contact' )});
const compiledSchema = x.object({ datasetId: x.string().xpath('/dataset/@id'), title: x.string().xpath('/dataset/title/text()'), metadata: x.object({ source: x.string().xpath('./source'), generatedAt: x.string().xpath('./generatedAt') }).xpath('/dataset/metadata'), labels: x.array(x.string(), '/dataset/labels/label'), people: x.array(personSchema, '//person')}).compile();
const result = compiledSchema.parseSync(xml);compile() keeps the same public API, but the fastest path is available only for schema shapes that can be lowered to fixed XML event dispatch.
The schema above combines the common fast-path shapes in one compiled schema: absolute element and attribute selectors, direct text() selectors, a descendant array boundary with //person, relative selectors inside each person item, nested objects, scalar arrays, object arrays, optional fields, and transforms. Numeric validation like .int() is still applied after text extraction.
Fast-path friendly shapes:
| Shape | Example |
|---|---|
| Absolute element paths | /catalog/book/title |
| Descendant element paths | //book |
| Absolute or relative attributes | /catalog/book/@id, ./@id |
| Relative fields inside object or array items | ./title, ./author/name |
| Direct text selection | /message/text(), ./title/text() |
| Objects with scalar fields | x.object({ title: x.string().xpath('./title') }).xpath('/book') |
| Arrays of scalars or objects | x.array(x.string(), '/tags/tag'), x.array(bookSchema, '/catalog/book') |
| Nested objects, optional fields, transforms | x.object({...}).optional().transform(...) |
Shapes that fall back to the normal converter path:
| Shape | Example |
|---|---|
| Wildcards | /catalog/* |
| Predicates | //book[@id="1"], //book[1] |
Ambiguous relative paths without ./ | title |
| Nested arrays | x.array(x.array(x.string(), './value'), '/group') |
| Arrays that define both an array XPath and an element XPath | x.array(x.string().xpath('./title'), '/book') |
| Custom or unsupported schema wrappers | User-defined schema subclasses |
Fallback preserves behavior, so these schemas still parse correctly after compile(). They just do not get the dispatch fast path.
Schema Reuse
Section titled “Schema Reuse”// ❌ Creates new schema every timefunction parseUser(xml: string) { const schema = x.object({...}); return schema.parseSync(xml);}
// ✅ Define schema once, reuseconst userSchema = x.object({...});
function parseUser(xml: string) { return userSchema.parseSync(xml);}Next Steps
Section titled “Next Steps”- Explore Schema Types for detailed API reference
- Learn XPath patterns for element selection
- See Transformations for data processing
- Check Examples for real-world patterns