XML to C# Class Generator
Paste an XML document to generate matching C# classes or records, with XmlSerializer attributes, nullable reference types, and List<T> or array options.
What this tool generates
Paste an XML document and get ready-to-use C# class or record types — attributes become properties tagged [XmlAttribute], elements that repeat under the same parent become a List<T> or T[], and nested elements become nested types. It runs entirely within your active Blazor Server session: your XML is parsed in-memory as part of this page's live connection and is never sent to a separate API, written to disk, or logged. Working with JSON instead? See the JSON to C# Class/Record Generator. Starting from a SQL CREATE TABLE script rather than a document? See the SQL Table to C# Entity Generator.
Worked example
With the default options (classes, XmlSerializer attributes on, List<T>, PascalCase names), this XML:
<order id="42">
<customer-name>Ada Lovelace</customer-name>
<item>Keyboard</item>
<item>Mouse</item>
</order>
generates:
using System.Collections.Generic;
using System.Xml.Serialization;
[XmlRoot("order")]
public class Order
{
[XmlAttribute]
public int Id { get; set; }
[XmlElement("customer-name")]
public string CustomerName { get; set; }
[XmlElement("item")]
public List<string> Item { get; set; }
}
Notice id (an XML attribute) becomes an int property tagged [XmlAttribute], customer-name becomes CustomerName with an [XmlElement("customer-name")] tag restoring the original name for correct round-tripping, and the two repeated <item> siblings collapse into a single List<string> tagged [XmlElement("item")] — that attribute tells XmlSerializer to read/write each list entry as its own repeated <item> sibling rather than wrapping them in a container element.
How repeated and shared structures are deduplicated
Two elements — anywhere in the document, not just siblings under the same parent — collapse into one shared class when they have the exact same attribute and child-element names and types, as long as that shape has at least two members. This is a genuine structural comparison, not a name match: an <Address> (say, city + zip) used under both <Customer> and <Supplier> generates one shared Address class as long as both instances have identical members. Conversely, if two elements happen to share the same tag name but have different members, they do not get incorrectly merged — the second one gets a disambiguated name (Address2, and so on) instead of silently reusing the first shape and losing fields.
The two-or-more-members threshold specifically avoids a confusing edge case: a trivial single-member wrapper, like a <Customer> and a <Supplier> that each contain nothing but one <address> child, would otherwise have an identical one-member shape and merge into a single class regardless of their different names — technically valid, working code, but a property named Supplier typed as Customer reads like a mistake even when it isn't one. Below that threshold, each tag name always keeps its own class; the Address type they both point to is still shared, since that shape has two members. If a repeated element's own occurrences don't perfectly agree with each other (see the mixed-type rule below), every attribute and child seen across all occurrences is included in the merged class.
Type inference and mixed-type resolution
Every attribute value and leaf element's text is checked in order: bool for exactly true/false, then int, then long (if the value is too large for int), then decimal (if it has a decimal point or exceeds long's range), then Guid, then DateTime for ISO 8601-shaped text, falling back to string for everything else. When the same repeated element's occurrences don't all agree — one <Price> is 10 and another is 10.50 — the type widens to cover both: int + decimal → decimal. Any other mismatch (bool vs. int, DateTime vs. plain text, and so on) falls back to string rather than guessing, since a numeric-widening rule doesn't make sense between unrelated shapes.
Namespaces, declarations, and comments
An XML declaration (<?xml version="1.0"?>) and XML comments are simply not part of the element tree this tool walks, so they never affect the generated classes either way. XML namespaces (xmlns) are a different story — this tool ignores them by design and generates properties from local element/attribute names only, showing a notice above the output whenever a namespace is detected. That keeps the common case (most hand-written or config-style XML doesn't rely on namespace-qualified round-tripping) simple, but it means the generated classes need manual Namespace= arguments added to their XmlRoot/XmlType attributes before they'll correctly deserialize a namespace-qualified document with XmlSerializer.
Records vs classes, and what "XmlSerializer attributes" off means
A C# record gives you value-based equality and a concise ToString() for free; a class is the more traditional choice. Both work identically with XmlSerializer as long as properties have public getters and setters, which is what this generator produces either way. Turning XmlSerializer attributes off produces plain, attribute-free POCOs — useful if you just want to see the shape of the data, or you already have your own serialization configuration. Be aware that without those attributes, the resulting classes will not correctly round-trip through XmlSerializer on their own: attribute-sourced values would be misread as elements, and any renamed property would no longer match its source XML name.
Common errors
| Error / behavior | Cause | Fix |
|---|---|---|
| "Paste an XML document to generate C# classes." | Input is empty or whitespace-only | Paste a real XML document |
| "Malformed XML at line X, column Y: ..." | Invalid XML syntax — an unclosed tag, a stray &, or a mismatched quote | XDocument.Parse reports the exact location; fix that line first |
| "The root element has no attributes or child elements..." | The root is just text or an empty tag, e.g. <Root/> or <Root>text</Root> | There's no shape to generate a class from — paste a document whose root actually has structure |
A generated class named Address2 | Two same-named elements had genuinely different members, so they were kept as separate classes rather than merged | Expected behavior — rename one in your own code afterward if you'd prefer a different name |
How to use the generated classes
The class names in your generated output depend on your actual XML's root element, but the deserialization pattern is always the same:
using System.IO;
using System.Xml.Serialization;
var serializer = new XmlSerializer(typeof(Root)); // "Root" -> your generated root class name
using var reader = new StringReader(xmlString);
var model = (Root)serializer.Deserialize(reader);
// Serializing back to XML works the same way, in reverse:
using var writer = new StringWriter();
serializer.Serialize(writer, model);
string xmlOutput = writer.ToString();
How to use this tool
- Paste an XML document into the input editor.
- Choose classes or records, nullable reference types, arrays or
List<T>, PascalCase naming, and whether to emitXmlSerializerattributes. - Click Generate.
- Copy the output. If a namespace notice appears, review whether you need to add
Namespace=to the root attribute by hand.
XML input
<order id="42"> <customer-name>Ada Lovelace</customer-name> <item>Keyboard</item> <item>Mouse</item> </order>
Generated C# classes
C# output