Skip to content
datahacks.dev
XML

Converting XML to YAML for Config Review and What It Breaks

Convert an XML config to YAML so a person can read and diff it, and learn why a YAML 1.1 loader turns the strings no and NO into false and 0755 into 493.

7 min read
Summarize in AI
Converting XML to YAML for Config Review and What It Breaks

XML configuration files are hard to review by eye. The tags double the line count, the closing tags carry no information and a one-word change is buried in markup. A common trick is to convert the file to YAML for the review, read or diff that, and keep the XML as the thing that is deployed. It works well for the purpose, and it has a trap that is specific to YAML.

The conversion

The pipeline is two libraries. xmltodict makes a dictionary out of the XML, with the conventions described in the JSON article, and PyYAML writes it out.

x2y.pyPython
import xmltodict, yaml
d = xmltodict.parse(open("library.xml", "rb").read(), force_list=("book", "author"))
print(yaml.safe_dump(d, sort_keys=False, allow_unicode=True))
YAML
library:
  '@updated': '2026-10-01'
  book:
  - '@id': b1
    '@available': 'true'
    title: Cache Patterns
    author:
    - Ana Lee
    - Bo Chen
    price:
      '@currency': USD
      '#text': '29.50'
    note: Covers R&D
  - '@id': b2
    '@available': 'false'
    title: SQL Tuning, 2nd Edition
    author:
    - Cy Ortiz
    price:
      '@currency': USD
      '#text': '44.00'
  - '@id': b3
    '@available': 'true'
    title: Indexes "Explained"
    author:
    - Di Park
    price:
      '@currency': VND
      '#text': '250000'
    note: null

Several things stand out. The line count dropped, and every field is on one line with its value, which is why this is easier to read than the XML. sort_keys=False keeps the keys in the order of the document, so a diff between two converted files shows what changed in the order a person would expect, whereas the default sorts them alphabetically and makes title jump above @id. force_list makes book and author lists even where they have one entry, so the structure does not change shape when a book gets a second author, and a diff of a one-author and a two-author file shows an added line. The XML conventions remain: attributes with an @ prefix, text beside attributes under #text. They are quoted because a bare @ cannot start a YAML scalar, and #text would otherwise read as a comment. The empty <note/> is null and the absent note has no line.

The output is a faithful picture of the XML and not a good hand-written YAML config. It would look different if a person had designed it, with price: 29.50 and currency: USD side by side. Use it for reading.

The quoting is doing real work

Notice that '2026-10-01', 'true', '29.50' and '250000' all appear in quotes. safe_dump quotes a string whenever an unquoted scalar would be read back as a different type: true is a boolean, 29.50 a float, 2026-10-01 a date, 250000 an integer. So the quotes are the converter saying "this is text". Now a config where that matters:

Python
c = xmltodict.parse("<cfg><flag>no</flag><ver>1.10</ver><country>NO</country><port>0755</port></cfg>")
print(yaml.safe_dump(c, sort_keys=False))
YAML
cfg:
  flag: 'no'
  ver: '1.10'
  country: 'NO'
  port: '0755'

All four values are quoted, correctly. no and NO are booleans for a YAML 1.1 parser (the Norway problem, where the country code NO becomes false), 1.10 would be the float 1.1 and lose a digit that matters in a version, and 0755 would be read as octal. PyYAML knows, so it quotes them. The trap is what happens to this file next. If a person edits the converted YAML, drops the quotes because they look noisy, and the file is loaded back:

Python
print(yaml.safe_load("flag: no\nver: 1.10\ncountry: NO\nport: 0755"))
Text
{'flag': False, 'ver': 1.1, 'country': False, 'port': 493}

The flag is False, the country code is False, the version is 1.1 and the permission is the integer 493, which is 0755 read as octal. Nothing raised an error. Every one of those is a silent change of meaning, and this is why a converted file should be read and compared, never edited and loaded. The ambiguity belongs to YAML 1.1; YAML 1.2 narrowed the booleans to true and false, but the widely used PyYAML implements 1.1, and tools differ, so a file that is correct in one loader is different in another.

A review workflow that works

Used as a viewing aid, the conversion is helpful and safe. The recipe I use for configuration that lives in XML (Maven POMs, Tomcat or Spring configs, application settings from older systems):

  1. Convert the old and the new version with the same script and the same options.

  2. Diff the YAML files, not the XML.

  3. Read the diff for changed values; ignore moved lines if the order is not significant for your system.

  4. Deploy the XML.

Step 1 matters more than it looks. If the two versions are converted with different options, or different library versions, the diff will show differences that come from the converter. Pin the library version in the script and keep the script with the repository. For a real review, also look at the XML diff for anything the conversion hides, which is a surprising amount: comments (the YAML has none of the XML's comments, and the comments in a config often explain why a value is set), processing instructions, namespace declarations, and the exact whitespace in text values.

On the command line, a YAML view of a file is available without writing Python. The Python yq package (the one that wraps jq) installs an xq command, and xq -y '.library.book[0]' library.xml printed the first book as YAML with the same '@id' and '@available' keys as the script above. Use one tool for both sides of a review, since other yq implementations choose different prefixes for attributes. The wider topic is covered in using yq to read XML.

The structural mismatch

XML and YAML are both trees, but they spend their syntax differently. XML repeats names for lists, YAML has explicit sequences. XML distinguishes attributes from children, YAML has one kind of key. XML is ordered and YAML mappings are formally unordered, though the common loaders keep order. A converter bridges those with conventions, and the conventions leak into the output. You have already seen the @ and #text keys. A second leak is the single-child rule: without force_list, one <server> is a mapping and two are a list, so a config file changes shape when a second server is added, and a diff shows a complete restructuring instead of an addition. Always force the lists you know about.

A third leak is types. Everything coming out of the XML is a string, and safe_dump quotes the strings that look like something else. A hand-written YAML would write port: 8080 unquoted, as an integer. In the converted file you see port: '8080', which looks wrong but is exactly right: the XML never said it was a number. If you want integers in the YAML, convert them in the dictionary before dumping, by name, and accept that you are now interpreting the file.

Going back is a different job

A converted YAML can be turned back into XML (xmltodict.unparse), and for a file produced by the same script it will usually round-trip. For a file a person has edited, treat the result as untrusted: check with the XML schema or the application's own validator, and compare element order and attribute spelling against the original. The risks are the ones above, plus the loss of comments and of the exact formatting. The reverse mapping in general is covered in converting JSON to XML with sensible element names.

For the mapping rules the converter follows, see converting XML to JSON and the attribute problem. The YAML side of the same conversions is in converting JSON to YAML on the command line. PyYAML is documented at pyyaml.org, and the YAML 1.2 changes to booleans are in the YAML 1.2.2 specification. If you want to look at a path before you convert, use the XPath tester.

When the YAML must be loaded back as data

The YAML becomes the source of truth. If a team will maintain the converted file by hand and an application will read it, write the YAML as a configuration format, designed by a person, with its own tests. A mechanical conversion carries XML shapes that no one would choose.

Values include booleans-in-disguise. Country codes, answers like y and n, on/off switches and octal-looking numbers need explicit quoting, and a YAML 1.1 loader will not warn you if they lack it. Use a loader that reports the type of every scalar, or validate against a schema.

Comments matter. XML comments carry the explanation for a setting, and the conversion drops them. Review the XML for the comments before you trust the YAML view.

The tool chain is fixed on XML. If everything downstream reads XML, keep the editing in XML and use YAML only as a viewer.

Also inYAML
Share

Keep reading

Get new articles by email

We send one email when a new article is published. No spam.

Unsubscribe in one click. No spam, ever.