Code Generators
XML to TypeScript Converter
Paste an XML sample, get typed TypeScript models — names, nesting and nullability inferred for you.
In short
XML to TypeScript parses an XML sample into a data model — attributes included, element text under a text field — and infers interfaces from it with quicktype, in your browser. The root element becomes a nested type under Root, repeated elements become Item[], element text such as total (228.99) is typed number, and a Convert class validates payloads against the result.
By NaveenKumar T · Updated
- Typed models from one sample
- Nested objects become types
- Nullability inferred
Runs entirely in your browser — nothing you paste or open here is uploaded, logged, or stored.How we handle data →
Example
Example: XML order payload to TypeScript
The same order as XML, with data split between attributes (id, status, paid, sku, qty, price) and element text (name, email, total) — the split that decides which TypeScript fields are typed as numbers and which stay strings. The right-hand side is the exact output of this tool.
<order id="ord_9F2KX" status="processing" paid="true">
<customer>
<name>Ada Lovelace</name>
<email>ada@example.com</email>
</customer>
<items>
<item sku="KB-75" qty="1" price="149.99"/>
<item sku="MS-12" qty="2" price="39.5"/>
</items>
<total>228.99</total>
</order>// To parse this data:
//
// import { Convert, Root } from "./file";
//
// const root = Convert.toRoot(json);
//
// These functions will throw an error if the JSON doesn't
// match the expected interface, even if the JSON is valid.
export interface Root {
order: Order;
}
export interface Order {
customer: Customer;
items: Items;
total: number;
id: string;
status: string;
paid: string;
}
export interface Customer {
name: string;
email: string;
}
export interface Items {
item: Item[];
}
export interface Item {
sku: string;
qty: string;
price: string;
}
// Converts JSON strings to/from your types
// and asserts the results of JSON.parse at runtime
export class Convert {
public static toRoot(json: string): Root {
return cast(JSON.parse(json), r("Root"));
}
public static rootToJson(value: Root): string {
return JSON.stringify(uncast(value, r("Root")), null, 2);
}
}
function invalidValue(typ: any, val: any, key: any, parent: any = ''): never {
const prettyTyp = prettyTypeName(typ);
const parentText = parent ? ` on ${parent}` : '';
const keyText = key ? ` for key "${key}"` : '';
throw Error(`Invalid value${keyText}${parentText}. Expected ${prettyTyp} but got ${JSON.stringify(val)}`);
}
function prettyTypeName(typ: any): string {
if (Array.isArray(typ)) {
if (typ.length === 2 && typ[0] === undefined) {
return `an optional ${prettyTypeName(typ[1])}`;
} else {
return `one of [${typ.map(a => { return prettyTypeName(a); }).join(", ")}]`;
}
} else if (typeof typ === "object" && typ.literal !== undefined) {
return typ.literal;
} else {
return typeof typ;
}
}
function jsonToJSProps(typ: any): any {
if (typ.jsonToJS === undefined) {
const map: any = {};
typ.props.forEach((p: any) => map[p.json] = { key: p.js, typ: p.typ });
typ.jsonToJS = map;
}
return typ.jsonToJS;
}
function jsToJSONProps(typ: any): any {
if (typ.jsToJSON === undefined) {
const map: any = {};
typ.props.forEach((p: any) => map[p.js] = { key: p.json, typ: p.typ });
typ.jsToJSON = map;
}
return typ.jsToJSON;
}
function transform(val: any, typ: any, getProps: any, key: any = '', parent: any = ''): any {
function transformPrimitive(typ: string, val: any): any {
if (typeof typ === typeof val) return val;
return invalidValue(typ, val, key, parent);
}
function transformUnion(typs: any[], val: any): any {
// val must validate against one typ in typs
const l = typs.length;
for (let i = 0; i < l; i++) {
const typ = typs[i];
try {
return transform(val, typ, getProps);
} catch (_) {}
}
return invalidValue(typs, val, key, parent);
}
function transformEnum(cases: string[], val: any): any {
if (cases.indexOf(val) !== -1) return val;
return invalidValue(cases.map(a => { return l(a); }), val, key, parent);
}
function transformArray(typ: any, val: any): any {
// val must be an array with no invalid elements
if (!Array.isArray(val)) return invalidValue(l("array"), val, key, parent);
return val.map(el => transform(el, typ, getProps));
}
function transformDate(val: any): any {
if (val === null) {
return null;
}
const d = new Date(val);
if (isNaN(d.valueOf())) {
return invalidValue(l("Date"), val, key, parent);
}
return d;
}
function transformObject(props: { [k: string]: any }, additional: any, val: any): any {
if (val === null || typeof val !== "object" || Array.isArray(val)) {
return invalidValue(l(ref || "object"), val, key, parent);
}
const result: any = {};
Object.getOwnPropertyNames(props).forEach(key => {
const prop = props[key];
const v = Object.prototype.hasOwnProperty.call(val, key) ? val[key] : undefined;
result[prop.key] = transform(v, prop.typ, getProps, key, ref);
});
Object.getOwnPropertyNames(val).forEach(key => {
if (!Object.prototype.hasOwnProperty.call(props, key)) {
result[key] = transform(val[key], additional, getProps, key, ref);
}
});
return result;
}
if (typ === "any") return val;
if (typ === null) {
if (val === null) return val;
return invalidValue(typ, val, key, parent);
}
if (typ === false) return invalidValue(typ, val, key, parent);
let ref: any = undefined;
while (typeof typ === "object" && typ.ref !== undefined) {
ref = typ.ref;
typ = typeMap[typ.ref];
}
if (Array.isArray(typ)) return transformEnum(typ, val);
if (typeof typ === "object") {
return typ.hasOwnProperty("unionMembers") ? transformUnion(typ.unionMembers, val)
: typ.hasOwnProperty("arrayItems") ? transformArray(typ.arrayItems, val)
: typ.hasOwnProperty("props") ? transformObject(getProps(typ), typ.additional, val)
: invalidValue(typ, val, key, parent);
}
// Numbers can be parsed by Date but shouldn't be.
if (typ === Date && typeof val !== "number") return transformDate(val);
return transformPrimitive(typ, val);
}
function cast<T>(val: any, typ: any): T {
return transform(val, typ, jsonToJSProps);
}
function uncast<T>(val: T, typ: any): any {
return transform(val, typ, jsToJSONProps);
}
function l(typ: any) {
return { literal: typ };
}
function a(typ: any) {
return { arrayItems: typ };
}
function u(...typs: any[]) {
return { unionMembers: typs };
}
function o(props: any[], additional: any) {
return { props, additional };
}
function m(additional: any) {
return { props: [], additional };
}
function r(name: string) {
return { ref: name };
}
const typeMap: any = {
"Root": o([
{ json: "order", js: "order", typ: r("Order") },
], false),
"Order": o([
{ json: "customer", js: "customer", typ: r("Customer") },
{ json: "items", js: "items", typ: r("Items") },
{ json: "total", js: "total", typ: 3.14 },
{ json: "id", js: "id", typ: "" },
{ json: "status", js: "status", typ: "" },
{ json: "paid", js: "paid", typ: "" },
], false),
"Customer": o([
{ json: "name", js: "name", typ: "" },
{ json: "email", js: "email", typ: "" },
], false),
"Items": o([
{ json: "item", js: "item", typ: a(r("Item")) },
], false),
"Item": o([
{ json: "sku", js: "sku", typ: "" },
{ json: "qty", js: "qty", typ: "" },
{ json: "price", js: "price", typ: "" },
], false),
};
Compare "total" (element text, typed number) with "price" and "qty" (attributes, kept as string). The root <order> element became a nested Order type under Root.
Learn more
From XML sample to TypeScript types
Hand-writing model classes from an XML payload means deciding, field by field, which parser reads which node — this page automates that decision. quicktype first parses your document into a data model, attributes included, then runs the same type inference it uses for JSON against the result.
number is a 64-bit float, so an integer ID above 2^53 rounds silently — and the runtime Convert cast will not catch it, because it checks the type, not the range. Keep 64-bit IDs as strings on the wire, or parse with a reviver that turns them into BigInt, and change the interface field to string or bigint by hand.
How XML elements and attributes become TypeScript types
The XML is parsed into a data model before inference (attributes included, text nodes named "text"), so the root element becomes a nested type: <order> is a field of type Order on the top-level Root rather than the top level itself. Child elements become fields, and repeated elements — the two <item> siblings — are collected into Item[] under an Items wrapper type that mirrors the <items> container.
Attributes and element text are typed differently, and this matters: element text is type-inferred, so <total>228.99</total> becomes number, while attribute values are kept as they were written — qty="1" and price="149.99" stay string in TypeScript. Prefer element text for numeric data you want typed, or cast attribute values after decoding. One more trap: a single <item> would not be recognized as a list, so make sure repeated elements appear at least twice in the sample.
Reading the XML in TypeScript with the generated interfaces
This page parses your XML with fast-xml-parser before inference runs, so the quickest way to make the generated interfaces true in your own code is to use the same parser with the same two options: keep attributes, and give their names no prefix. Stringify the parsed object, hand it to Convert.toRoot, and you get the compile-time types and the runtime check together. Remember the single-element trap: an <items> element with one <item> child parses as an object rather than an array, and Convert will throw on it.
// npm i fast-xml-parser
import { XMLParser } from "fast-xml-parser";
import { Convert, type Root } from "./Root";
// Same options this page uses: attributes kept, names unprefixed.
const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: "" });
const parsed = parser.parse(xmlText);
const root: Root = Convert.toRoot(JSON.stringify(parsed));
console.log(root.order.items.item[0].sku); // "KB-75"Help
Frequently asked questions
Everything you need to know about the XML to TypeScript.
01How do I generate TypeScript classes from XML?
Paste an XML document and press Convert. It is first parsed into a data model — attributes included — then typed: the root element becomes a nested type, repeated elements become Item[], and the result downloads as Root.ts.
02How do I get the same object shape from XML at runtime?
Use fast-xml-parser with the two options this page uses — ignoreAttributes: false and attributeNamePrefix: "" — then JSON.stringify the result and pass it to Convert.toRoot, which validates it against the generated interfaces.
03Why does Convert.toRoot throw on a document with a single item?
fast-xml-parser returns one child as an object, not a one-element array, so items.item no longer matches Item[] and the runtime cast rejects it. Pass isArray: (name) => name === "item" to the parser, or ensure the element is always repeated.
04Does Convert.toRoot catch every mismatch between the sample and a live XML document?
Only shape and type mismatches, not the array trap: a document whose only <item> collapses to an object instead of a one-element array is a shape TypeScript can validate, but fast-xml-parser produces that shape happily, so Convert.toRoot throws only after hitting the resulting object where Item[] was expected. Pass an isArray callback to the parser to prevent the case rather than catch it downstream.
Keep working