XML to TypeScript

Generate TypeScript interfaces from an XML document in the same layout as this site's XML to JSON converter — attributes under @attributes, text under #text, repeated elements as arrays, mixed slots as union types instead of guesses. Free, no sign-up — runs locally in your browser.

Your work is auto-saved in this browser only, so a refresh won't lose it. Nothing is uploaded.

…or drop an .xml file anywhere on this panel.

Why generate TypeScript interfaces from an XML sample?

TypeScript code consumes more XML than it admits to — feeds fetched in the browser, SVG and sitemap processing, config dialects, exports from tools that predate JSON. The usual path converts the XML to a JavaScript object and then works untyped, one runtime surprise at a time. Generating the interfaces from a real sample inverts that: every element and attribute in the sample gets a property with an inferred type, repeated elements become arrays, sometimes-absent elements become optional properties — and the layout deliberately matches this site's XML to JSON converter, attributes under "@attributes" and text under "#text", so the types describe the objects you will actually hold.

How to use this tool

  1. Paste the XML, upload an .xml file, or drop one on the panel. The document must be well-formed; malformed input is reported with a line and column, not converted.
  2. Optionally name the root interface; left blank, it takes the root element's name. Decide on type inference — on for samples you trust, off if values are identifiers that merely look numeric.
  3. Press Generate TypeScript interfaces. The root interface comes first.
  4. Read the notes. Sometimes-absent attributes, mixed content and always-empty elements are listed there — review them before you rely on the result.
  5. Copy or download, and pair the interfaces with the XML to JSON converter's output (or your own parser configured to the same layout) so the types and the data stay in step.

The mapping rules

Elements. A child element becomes a property; one that repeats anywhere becomes an array; one that is absent from some parents becomes an optional property. Attributes collect under "@attributes", each typed from its values, and optional there when some occurrences lack it. Text. A leaf element's text types the property — integer and decimal text are both number, true/false is boolean, and a slot that shows several shapes becomes their union rather than a guess. Text alongside child elements collects under "#text". Shapes. Elements with identical structure share one interface. Property names keep the XML name verbatim, quoted when it is not a valid identifier — nothing is renamed silently.

Worked example

Given this catalogue fragment (note the second book has no price):

<catalog count="2">
  <book id="bk101">
    <title>Go in Practice</title>
    <price>39.95</price>
  </book>
  <book id="bk102">
    <title>XML Field Guide</title>
  </book>
</catalog>

the converter emits:

export interface Catalog {
  "@attributes": { count: number };
  book: Book[];
}

export interface Book {
  "@attributes": { id: string };
  title: string;
  price?: number;
}

Every judgement in that output is the sample's own evidence: the array is the second book, the question mark is the missing price, the attribute types are the values themselves.

Frequently asked questions

Why do attributes live under "@attributes" instead of becoming properties?

Because in XML an attribute and a child element can share a name — <item id="1"><id>2</id></item> is legal — and flat properties would make one overwrite the other. Grouping attributes under "@attributes" (and text under "#text") is the same layout this site's XML to JSON converter produces, so these interfaces describe exactly the objects that converter gives you: generate the types and the JSON from one document and they fit. The prefix also makes the XML origin of a value visible at every use site.

Why is this property a union like number | string?

Because the sample shows both: the element held integer-looking text in one occurrence and plain text in another. TypeScript can say that precisely, so the generator does — a union is the sample's evidence written down, and it will push you to handle both cases at every use site, which is the point of having types. If you would rather have plain string everywhere, untick type inference and every scalar becomes string.

Why number for both integers and decimals?

TypeScript has one numeric type. Integer-looking text and decimal text both become number — there is no int/float distinction to preserve, unlike the Go, Java and Python generators on this site. If the distinction matters (a money field, say), the sample cannot express it either; rename the type at the use site.

Where does an element's text go when it also has children?

Under "#text". If the text is interleaved with the child elements — 'Hello <b>world</b> again' — the fragments are concatenated into that one property and the note says so, because the interleaving has no interface representation. A pure-text element gets no "#text" at all: its text simply types the property the parent declares for it. A root element holding only text becomes a type alias for its text type rather than a one-property interface.

What about namespaces, like <dc:title>?

The element name is used as written, prefix included, as a quoted property name ("dc:title") — TypeScript interfaces have no namespace concept to map it onto, and dropping the prefix would rename the data. If your documents redefine prefixes per document, one sample cannot tell the generator the spellings are the same element; normalise the sample first.

Can I turn off the number / boolean guessing?

Yes — untick 'Infer numeric and boolean types' and every text value is typed string. That is the conservative choice for XML you do not control: a product code of 007 would otherwise become the number 7. Inference is a convenience for samples you trust, and union types (previous question) are how it reports genuinely mixed evidence instead of guessing.

Related tools