JSON vs YAML vs XML: formats and pitfalls
Which format, in one rule
JSON is the default format of REST APIs; YAML is what people write by hand for configuration (Ansible playbooks, Kubernetes manifests, pipeline files); XML is the older, more verbose format that survives in NETCONF, SAML and SOAP. They all describe the same kinds of structure, nested key-value data and lists, so exam questions test whether you can read one, convert it to another, and spot what breaks it. The rule: machines talking to machines use JSON, humans editing files use YAML, and protocols standardised before about 2010 use XML.
The same data three times
One VLAN with two ports:
{
"vlan": {
"id": 10,
"name": "Sales",
"active": true,
"ports": ["Gi1/0/1", "Gi1/0/2"]
}
}
vlan:
id: 10
name: Sales
active: true
ports:
- Gi1/0/1
- Gi1/0/2
<vlan id="10">
<name>Sales</name>
<active>true</active>
<ports>
<port>Gi1/0/1</port>
<port>Gi1/0/2</port>
</ports>
</vlan>
Read the shapes first. JSON uses braces for objects, brackets for arrays, and double-quoted keys. YAML replaces all of that with indentation and a dash for each list item. XML uses a tag pair for every value, can carry data as an attribute (id="10") or as a child element, and has exactly one root element. XML has no native list: repetition of <port> is the list.
What Python makes of each
| Format | Parser | Result |
|---|---|---|
| JSON | json.loads(text) or json.load(file) | dict, list, str, int, float, bool, None |
| YAML | yaml.safe_load(text) (PyYAML, a third-party package) | The same Python types |
| XML | xml.etree.ElementTree.fromstring(text) | A tree of Element objects; every value is a string |
JSON true, false and null become True, False and None. The XML row is the surprising one: root.attrib["id"] is the string "10", not an integer, because XML itself carries no types. Types come from a schema (or a YANG model) or from your own conversion. The structure in the first example is read as data["vlan"]["ports"][0], which is "Gi1/0/1".
Pitfalls that cause real failures
JSON is strict. RFC 8259 allows only double-quoted strings, no comments and no trailing commas, and it does not permit NaN or Infinity as numbers. A stray comma after the last item is the classic invalid-JSON question. (Python’s json module is lenient about NaN by default, which is a library choice, not the standard.)
YAML indentation is syntax. The YAML specification says tab characters must not be used for indentation, so a tab in a playbook is an error, and so is inconsistent spacing. A list item at the wrong depth silently changes the structure rather than failing.
YAML guesses types. Under the YAML 1.2 core schema only true and false (in a few spellings) are booleans. Older YAML 1.1 rules, which PyYAML still follows, also treat yes, no, on and off as booleans. Running PyYAML 6.0.3 on this input shows the problem:
$ python3 -c "import yaml; print(yaml.safe_load('a: no\nb: 3.10\nd: 0777'))"
{'a': False, 'b': 3.1, 'd': 511}
The value no (for example the country code for Norway, in any case: NO behaves the same) becomes False, the version 3.10 becomes the float 3.1, and 0777 is read as octal 511. The fix is quoting: "no", "3.10", "0777". Also use safe_load, never plain load, on untrusted input, because the unsafe loader can construct arbitrary Python objects.
XML escapes and namespaces. A literal < or & in text must be written < or &. Elements can belong to a namespace (xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"), and a parser searching for the plain tag name will not find a namespaced element. This is exactly what NETCONF replies look like.
Where each one shows up
| Format | In network automation and DevOps |
|---|---|
| JSON | REST API requests and responses, RESTCONF with application/yang-data+json, Terraform state, Azure ARM templates |
| YAML | Ansible playbooks and inventories, Kubernetes and Docker Compose files, Azure Pipelines and GitHub Actions workflows |
| XML | NETCONF, SOAP, SAML assertions (SSO explained), some legacy device APIs |
YAML 1.2 was designed so that valid JSON is also valid YAML, so a JSON file can be fed to a YAML tool. The reverse is false: YAML comments, anchors and indentation have no JSON equivalent.
Which exams test this
| Exam | How it appears | Covered on this site |
|---|---|---|
| 200-901 | Software Development and Design (15%): compare XML, JSON and YAML and describe how each is parsed into Python data structures. The formats return in Understanding and Using APIs (reading the body of an HTTP response) and in Infrastructure and Automation (interpreting a RESTCONF or NETCONF reply and an Ansible playbook) | Software development and design explained, APIs explained |
| 200-301 | A minor topic, and it differs per version. v1.1 (until Feb 2, 2027): Automation and Programmability asks you to recognise the components of JSON-encoded data; XML and YAML are not named. v2.0 (from Feb 3, 2027): the JSON objective is gone; AI, and Network Operations and Management asks you to use Ansible to execute commands, so YAML appears only indirectly through playbooks | AI and operations explained (v2.0, Ansible and YAML playbooks) |
| AZ-400 | YAML only, not the comparison: Design and implement build and release pipelines asks you to develop pipelines in YAML, build reusable YAML templates and migrate classic pipelines to YAML | Build and release pipelines explained, YAML pipelines basics |
How questions are built around it
Most questions give a snippet and ask one of three things: which format is this, what is wrong with it, or what Python call or type results. The deciding details are small. { and [ mean JSON; indentation and - mean YAML; angle brackets mean XML. “Cannot be parsed” in a JSON snippet is nearly always a trailing comma, single quotes or a comment. “Human readable configuration file” points to YAML; “data exchanged between a client and a REST API” points to JSON; “namespace” or “attribute” points to XML. For Python, loads takes a string and load takes a file object, and safe_load is the safe YAML call to recognise.
Practice questions
Question 1. A script receives this REST response body: {name: Sales, vlan: 10,}. The call json.loads(body) raises an error. What is the problem?
- A. The number 10 must be written as a string
- B. JSON requires every line to be indented by four spaces
- C. json.loads only accepts a file object
- D. The keys are unquoted and there is a trailing comma
Show answer
Answer: D
The text is not valid JSON: keys must be double-quoted strings and a trailing comma after the last member is not allowed. Single quotes would also be invalid. JSON itself has no indentation rule, so spacing is not the cause, and the integer 10 is valid. json.loads accepts strings, so passing a string is not the issue either.
Question 2. A script reads an XML document with ElementTree and gets an element whose id attribute is written id=10. The script then compares root.attrib[id] with the integer 10 and the comparison is False. Why?
- A. XML values are parsed as strings, so the value must be converted to int
- B. ElementTree cannot read attributes, only child elements
- C. The id attribute is only available after validating a schema
- D. The document must be converted to YAML first
Show answer
Answer: A
XML carries no data types, so ElementTree returns attribute values and text as strings; the string 10 is not equal to the integer 10 until you convert it with int(). The attribute is present, so it is not missing. ElementTree handles attributes without a schema, and an XML document does not need to be YAML to be read.