XML to Parquet Converter

The most verbose data format into the most compact one. Records found without an XPath, real column types, and a file your query engine reads one column at a time.

To convert XML to Parquet, paste or drop your document above. The repeating record element is detected automatically, attributes and child elements become columns, each column is typed once from all its values, and DuckDB running in your browser writes the Parquet file with real BIGINT, DOUBLE, BOOLEAN, DATE and VARCHAR columns.

Want to filter records before writing? Open the app

The size difference is not marginal

XML is the most verbose format in common use. Every value is wrapped in its field name twice, once opening and once closing, and every value is text. A feed of a hundred thousand product records spends most of its bytes repeating the same twenty element names.

Parquet stores each column together, once, with knowledge of its type, and a repeated category string compresses to almost nothing. The same data routinely lands at a small fraction of the original size, and a query reading two fields reads two columns rather than parsing the whole document.

That is why this conversion is usually done at the point a feed enters storage, rather than every time somebody wants to query it.

Finding the records without asking you for a path

The hard part of reading XML as a table is deciding what a row is. A document has one root, and somewhere inside it is a repeating element that represents a record. Most tools make you supply an XPath expression for it.

That is a reasonable design and a bad first experience: you have to open the file, understand its structure, and write an expression before you see anything. Here the repeating element is found by looking for the deepest element name that occurs many times as a sibling, which is the record element in essentially every real feed.

Element children become columns, attributes become columns too, and nesting inside a record flattens with dot notation. What the detection picked is stated under the result, so if it guessed wrong on an unusual document you can see that immediately rather than wondering why the row count looks odd.

Types inferred, then declared

XML carries no types, so every type in the resulting file is an inference from the values. Each column is decided once, from all of its values:

  • BIGINT where every value is a whole number, which keeps counts and identifiers exact.
  • DOUBLE where the column has fractions in it.
  • BOOLEAN where every value is a lowercase true or false.
  • DATE where every value is an exact ISO date, which is what makes a date predicate push down instead of scanning.
  • VARCHAR for everything else, including padded codes and timestamps that carry an offset.

Those types are then handed to DuckDB explicitly rather than left to be sniffed a second time, so the file's schema is exactly the one the preview showed. Without that, a column of order references could be re-read as an integer and every value silently changed.

Attributes are data too

XML has two places to put a value and different specifications choose differently. <product id="SKU-101"> and <product><id>SKU-101</id></product> carry the same fact, and plenty of documents use both at once.

So attributes become columns alongside element children rather than being ignored. A converter that reads only child elements loses the identifier on the majority of real feeds, because the identifier is very often an attribute.

Where an attribute and a child element share a name, both survive as separate columns rather than one overwriting the other. It is rare, and when it happens losing one silently would be much worse than an extra column.

Flattening, nulls and the row count

Nesting inside a record flattens into dotted columns, so an address element with a city inside it becomes address.city. Past four levels the value is kept as text in a VARCHAR column, which DuckDB and most engines can still query if you need to go further.

Repeated child elements inside one record are also kept as text rather than being spread into extra rows. The row count always equals the record count, which matters because every aggregate computed on the file afterwards depends on it.

An empty element becomes a null rather than an empty string, and a field absent from some records is null there, because the columns are the union of every field across every record.

Running it locally

DuckDB compiled to WebAssembly writes the file inside your browser tab. The engine is a few megabytes on first use and cached afterwards.

Nothing is uploaded, which for a feed usually means supplier pricing, customer records or transaction detail staying on your machine. The practical ceiling is your tab's memory; a hundred megabyte document is comfortable, and past that the full editor streams.

Frequently Asked Questions

Do I need an XPath or an XSD?

Neither. The record element is found from the document's structure and named under the result. If you have an XSD it describes more than this can infer, but you do not need it to get a file.

How can it type anything when XML has no types?

By reading every value in each column. A column becomes BIGINT only if every value is a whole number, DATE only if every value is an exact ISO date, and so on. Those inferences are then declared to DuckDB explicitly so the file's schema matches the preview.

Are attributes included as columns?

Yes, alongside child elements. Many feeds put the identifier in an attribute, so ignoring attributes would produce a file whose rows cannot be distinguished.

Will the Parquet have the same number of rows as the feed has records?

Always. Repeated child elements inside a record are kept as text rather than spread into extra rows, because a row count that silently differs from the record count invalidates every aggregate computed afterwards.

Snappy or Zstd?

Snappy unless you have a reason. Every Parquet implementation reads it and it decompresses fast. Zstd is meaningfully smaller and every current reader handles it, which suits archiving a large feed.

Is the document uploaded?

No. It is parsed and written by DuckDB compiled to WebAssembly inside your browser tab. For a feed carrying supplier pricing or customer detail that is usually the deciding factor.

Store the feed properly

Records found for you, real types, columnar and compressed, all in your browser.

Back to the converter