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');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