Free & open source — no account required
Free & open source — no account required
This technical guide provides an in-depth analysis of the xml to typescript engine, best practices for implementation, and data security standards.
Converting XML to TypeScript types has two paths: inferring types from XML samples at development time, or generating types from a formal XSD (XML Schema Definition) at build time. The sample-inference approach is quick but incomplete — it misses optional elements and attribute constraints. XSD-driven code generation is authoritative but requires access to the schema. In practice, most teams combine both: start with a sample to get the rough shape, then validate against the XSD if available. This guide covers both approaches, plus building a typed SOAP client with auto-generated types, and using Zod for runtime XML validation when no XSD is available.
import { XMLParser } from 'fast-xml-parser';
// TypeScript interfaces for an enterprise XML response
interface UserAttribute {
'@_id': number;
'@_active': boolean;
Name: string;
Email: string;
Role: string[]; // always an array (isArray config)
Department?: string; // optional element
CreatedAt: string; // ISO timestamp as string
}
interface UsersResponse {
'@_totalCount': number;
'@_page': number;
'@_pageSize': number;
User: UserAttribute[];
}
interface ApiResponse {
Users: UsersResponse;
}
// Parser configured to match the TypeScript interface shape
const parser = new XMLParser({
ignoreAttributes: false,
attributeNamePrefix: '@_',
attributeValueProcessor: (_name, val) => {
if (val === 'true') return true;
if (val === 'false') return false;
if (!isNaN(Number(val)) && val !== '') return Number(val);
return val;
},
isArray: tag => ['User', 'Role'].includes(tag),
parseTagValue: true,
removeNSPrefix: true,
trimValues: true,
});
function parseUsersXml(xml: string): ApiResponse {
const parsed = parser.parse(xml) as ApiResponse;
return parsed;
}
const response = parseUsersXml(xmlString);
// response.Users.User is User[] — typed, no casting
console.log(response.Users['@_totalCount']); // number
console.log(response.Users.User[0].Name); // string
console.log(response.Users.User[0].Role); // string[]
// If the XML service has a published XSD, use it to generate authoritative TypeScript types
// Option 1: xsd2ts (simple, fast)
// npm install -D xsd2ts
// npx xsd2ts -i user-service.xsd -o src/types/user-service.ts
// Generated output from xsd2ts:
export interface UserType {
id: number; // xs:integer (required attribute)
active: boolean; // xs:boolean (required attribute)
Name: string; // xs:string (required element)
Email: string; // xs:string (required element)
Role: string[]; // xs:string (maxOccurs="unbounded")
Department: string | undefined; // xs:string (minOccurs="0")
CreatedAt: Date; // xs:dateTime (required element)
}
// Option 2: xsd-to-ts CLI for complex schemas with extensions, restrictions, etc.
// npm install -D xsd-to-ts
// npx xsd-to-ts user-service.xsd --out src/types/
// Option 3: For WSDL (SOAP service contracts), generate a full client:
// npm install strong-soap
// Generates types AND a callable client from the WSDL URL
// Option 4: For WCF/Java SOAP services — xjc (Java) generates Java classes
// Translate to TypeScript manually or use tools like dtsgenerator
// Build step integration (package.json)
{
"scripts": {
"gen:types": "xsd2ts -i schemas/*.xsd -o src/types/generated/",
"prebuild": "npm run gen:types"
}
}
// Typed SOAP client using the 'soap' package
// npm install soap @types/soap
import soap from 'soap';
// Types derived from WSDL (or manually written from documentation)
interface GetUserRequest {
userId: string;
includeRoles?: boolean;
}
interface UserResult {
id: string;
name: string;
email: string;
roles: { role: string[] };
department: string;
}
interface GetUserResponse {
GetUserResult: UserResult;
}
interface UserServiceClient {
GetUserAsync(args: GetUserRequest): Promise<[GetUserResponse, string, soap.Client]>;
GetUsersAsync(args: { page: number; pageSize: number }): Promise<[{ Users: UserResult[] }, string]>;
}
// Create typed SOAP client
async function createUserClient(wsdlUrl: string): Promise<soap.Client & UserServiceClient> {
return soap.createClientAsync(wsdlUrl) as Promise<soap.Client & UserServiceClient>;
}
// Usage
const client = await createUserClient('http://enterprise.internal/user-service?wsdl');
const [response] = await client.GetUserAsync({
userId: 'USR-001',
includeRoles: true,
});
const user = response.GetUserResult;
console.log(user.name); // string — fully typed
console.log(user.roles.role); // string[] — typed from WSDL
// Automatic SOAP envelope wrapping — you work with plain TypeScript objects,
// the soap library adds the <soap:Envelope><soap:Body>...</soap:Body></soap:Envelope> wrapper
// When you have XML samples but no formal schema, use Zod for runtime validation
// after parsing XML to JSON
import { z } from 'zod';
import { XMLParser } from 'fast-xml-parser';
// Define the expected shape with Zod
const UserSchema = z.object({
'@_id': z.coerce.number().int().positive(),
'@_active': z.boolean(),
Name: z.string().min(1),
Email: z.string().email(),
Role: z.array(z.string()),
Department: z.string().optional(),
CreatedAt: z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/),
});
const UsersResponseSchema = z.object({
'@_totalCount': z.coerce.number(),
'@_page': z.coerce.number(),
User: z.array(UserSchema),
});
const ApiResponseSchema = z.object({
Users: UsersResponseSchema,
});
type ApiResponse = z.infer<typeof ApiResponseSchema>;
// Parse + validate pipeline
function parseAndValidate(xml: string): ApiResponse {
const parser = new XMLParser({
ignoreAttributes: false,
attributeNamePrefix: '@_',
isArray: tag => ['User', 'Role'].includes(tag),
removeNSPrefix: true,
trimValues: true,
});
const raw = parser.parse(xml);
const result = ApiResponseSchema.safeParse(raw);
if (!result.success) {
const errors = result.error.format();
throw new Error(`XML validation failed: ${JSON.stringify(errors, null, 2)}`);
}
return result.data;
}
// If the XML service changes its schema (adds a required field, changes a type),
// the Zod validation catches it immediately at the parse boundary
// Runtime type guard — verify parsed XML matches the expected interface
function isUserResponse(value: unknown): value is ApiResponse {
if (typeof value !== 'object' || value === null) return false;
const obj = value as Record<string, unknown>;
if (!('Users' in obj) || typeof obj.Users !== 'object') return false;
const users = obj.Users as Record<string, unknown>;
if (!Array.isArray(users.User)) return false;
return users.User.every(u =>
typeof u === 'object' && u !== null &&
typeof (u as any)['@_id'] === 'number' &&
typeof (u as any).Name === 'string' &&
typeof (u as any).Email === 'string'
);
}
// Usage
const raw = parser.parse(xmlString);
if (!isUserResponse(raw)) {
throw new Error('Unexpected XML response shape — possible schema change');
}
// raw is now typed as ApiResponse
const firstUser = raw.Users.User[0]; // UserAttribute — fully typed
// Assertion function (throws rather than returning boolean)
function assertUserResponse(value: unknown): asserts value is ApiResponse {
if (!isUserResponse(value)) {
throw new TypeError(`Expected UserResponse, got: ${JSON.stringify(value).slice(0, 200)}`);
}
}
isArray to match the TypeScript interface exactly: If your interface says Role: string[], the parser must always return an array for that element — even when only one <Role> exists. Mismatched configuration means the TypeScript interface lies: the type says array but at runtime you get a string.removeNSPrefix: true in fast-xml-parser means your TypeScript interfaces use clean names like Body instead of 'soap:Body'. Don't model namespaces in TypeScript types unless you're doing round-trip XML generation that requires them.Q: How do I handle XML elements that can be either an object or an array depending on the count?
A: This is the core problem with XML typing. Use isArray: tag => tag === 'YourTag' in fast-xml-parser to force it to always return an array. Then type it as T[] in your TypeScript interface. If you can't change the parser config, use type T = Item | Item[] and unwrap it with a helper: function toArray<T>(v: T | T[]): T[] { return Array.isArray(v) ? v : [v]; }.
Q: How do I generate TypeScript types from a WSDL file?
A: The strong-soap package reads a WSDL URL and generates both a callable client and TypeScript-compatible method definitions. For static generation, openapi-generator-cli supports WSDL-to-TypeScript codegen. If the WSDL is complex (WS-Security, attachments), the zeep Python library generates Python types which you can cross-reference to write TypeScript manually.
Q: What's the performance cost of XML vs JSON parsing in TypeScript?
A: fast-xml-parser processes ~1GB/s on a modern CPU — fast enough for most use cases. JSON parsing via JSON.parse is roughly 5-10x faster on equivalent data because JSON has simpler syntax and browsers/Node.js have native C++ implementations. For high-throughput XML ingestion (>10MB/s), consider a streaming SAX parser (saxes) instead of a DOM-style parser.
Is the processing local-only?
Absolutely. TypeMorph operates entirely within your browser's sandbox. We use Web Workers for high-performance computation without ever transmitting your JSON, SQL, or API data to a remote server.
Can I use this for enterprise projects?
Yes. The tool is designed for professional software engineers who require GDPR compliance and data privacy. It is trusted by developers at top-tier startups and financial institutions.
Why pasting proprietary company data into third-party web tools is a major liability, and how to stay safe.
A deep dive into combining Zod, React Query, and TypeScript for bulletproof API integration.
Code generation is just the beginning. Discover how a schema-first approach can eliminate 90% of your integration bugs.