XML to Python

Generate Python dataclasses from an XML document — attributes as fields, repeated elements as List, sometimes-absent elements as Optional with the dataclass field move reported, and every mixed-content and mixed-type judgement listed in the open. 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 Python dataclasses from an XML sample?

Python programs read a great deal of XML they did not design — RSS and Atom feeds, sitemaps, office-document internals, legacy SOAP and config dialects — and the hand-written half of every such reader is a set of classes that transcribe the format field by field. That transcription is mechanical, and the sample document already contains every fact it needs: which elements repeat, which attributes come and go, which leaf values look numeric. This converter reads those facts off the sample and writes the dataclasses, and — because a sample is evidence, not a contract — it reports every judgement call it made along the way.

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 class; left blank, it takes the root element's name. Decide on type inference — leave it on for samples you trust, off if values are identifiers that merely look numeric.
  3. Press Generate Python dataclasses. Classes are emitted in dependency order, so the file runs top to bottom.
  4. Read the notes. Optional fields, moved fields, mixed content and always-empty elements are all listed there — review them before you rely on the result.
  5. Copy or download, then pair the classes with your parser of choice (xml.etree, lxml, or a binding library) — the classes describe the shape; the walking is your code's job.

The mapping rules

Elements. A child element becomes a field; one that repeats anywhere becomes a List[…]; one that is absent from some parents becomes Optional[…] = None so absence stays visible, and optional fields move after the required ones because dataclasses demand it — the move is noted. Attributes become fields too, declared before the child fields. Text. A leaf element's text types the field — integer-looking text is int, decimal is float, true/false is bool, anything mixed across instances falls back to str with a note. Text alongside child elements collects into a text field. Shapes. Elements with identical structure share one dataclass. Field names are snake_case; whenever that differs from the XML name, a trailing comment keeps the original — 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:

from dataclasses import dataclass

from typing import List, Optional

@dataclass
class Book:
    id: str  # attribute
    title: str
    price: Optional[float] = None


@dataclass
class Catalog:
    count: int  # attribute
    book: List[Book]

Every judgement in that output is the sample's own evidence: the list is the second book, the Optional is the missing price, the attribute types are the digits themselves.

Frequently asked questions

Why dataclasses and not plain classes or TypedDicts?

Dataclasses are the closest Python counterpart to what the sample describes: a named shape with typed fields, generated with almost no boilerplate, in the standard library since Python 3.7. A TypedDict would describe a dictionary, not an object — and XML parses into element objects, not dicts, so the dataclass is the honest target. If your pipeline actually wants dictionaries, the shape still maps one-to-one: every field here is a key there.

Why is this field Optional?

Because the sample says it comes and goes: the element or attribute is present in some occurrences of its parent and missing from others. Optional[…] = None is Python's way of saying exactly that — absence becomes None rather than a fake zero or empty string. Repeated elements never become Optional: a missing one is simply an empty List, which already says 'none here'.

Why did my optional field move to the bottom of the class?

Because Python requires it: a dataclass field with a default value (and Optional fields get '= None') may not be followed by a field without one — the class would fail to define. So optional fields are emitted after the required ones, in their original relative order, and whenever that actually changes the order you wrote, the move is listed in the notes above the result. The XML order is still visible in the notes; the class order is Python's constraint, not a silent edit.

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

Into a field called text, commented as the element text content. If the text is interleaved with the child elements — 'Hello <b>world</b> again' — the fragments are gathered into that one field in order, and the note says so, because the interleaving itself has no dataclass representation. Pure-text elements don't get a text field at all: the text simply types the field the parent declares for them.

Can I turn off the int / float / bool guessing?

Yes — untick 'Infer numeric and boolean types' and every text value is typed str. That is the conservative choice for feeds you do not control: a product code of 007 would otherwise become the integer 7. Inference is a convenience for samples you trust; strings are the safe default, and the notes report every slot where the sample's evidence was mixed and str won by default.

What happens to namespaces, like <dc:title>?

The element or attribute name is used as written, prefix included: the field name is its snake_case form (dc_title) and the trailing comment keeps the original XML name, so nothing about the document is hidden. If your documents redefine prefixes per document, one sample cannot tell the generator the two spellings mean the same element — normalise the sample first.

Related tools