Python can deserialize JSON, YAML, and XML files into in-memory data, modify that data, and serialize it again. JSON and XML support is included in Python’s standard library. The examples here use the third-party PyYAML distribution for YAML.

Read Serialization first for the language-neutral process, or API Data Formats for the syntax and trade-offs of each representation.

Before the Code

Three actions that look similar serve different purposes:

python -m pip install PyYAML   install files into this Python environment
import yaml                    load the installed module into this program
yaml.safe_load(file)           deserialize YAML data

The detailed explanations are in:

Set Up the Example

Create and activate a virtual environment before installing the YAML dependency:

python3 -m venv .venv
source .venv/bin/activate
python -m pip install PyYAML

The json and xml.etree.ElementTree modules need no separate installation because they are part of the standard library.

The examples use the same logical data:

user
├── name: john
├── location
│   ├── city: Austin
│   └── state: TX
└── roles
    ├── admin
    └── user

JSON

Python’s json module converts common JSON values as follows:

JSONPython
objectdict
arraylist
stringstr
integer numberint
real numberfloat
true / falseTrue / False
nullNone

Given user.json:

{
  "user": {
    "name": "john",
    "location": {
      "city": "Austin",
      "state": "TX"
    },
    "roles": ["admin", "user"]
  }
}

Read it, change the city, and write a new file:

import json
from pathlib import Path
 
input_path = Path("user.json")
output_path = Path("user-updated.json")
 
with input_path.open(encoding="utf-8") as file:
    data = json.load(file)
 
for role in data["user"]["roles"]:
    print(role)
 
data["user"]["location"]["city"] = "Dallas"
 
with output_path.open("w", encoding="utf-8") as file:
    json.dump(data, file, indent=2, ensure_ascii=False)
    file.write("\n")

data is a dictionary. Changing it does not change user.json; the file changes only when the program writes to it.

load vs. loads, and dump vs. dumps

The final s means string:

FunctionDirection
json.load(file)File-like object to Python data
json.loads(text)JSON string or supported byte input to Python data
json.dump(data, file)Python data to a file-like object
json.dumps(data)Python data to a JSON string
json_text = '{"enabled": true}'
settings = json.loads(json_text)
 
new_text = json.dumps(settings)

JSON round trips are not guaranteed to preserve the exact Python type. For example, a tuple is written as a JSON array and read back as a list.

YAML with PyYAML

PyYAML is the distribution name; yaml is the import name:

python -m pip install PyYAML

Given user.yaml:

user:
  name: john
  location:
    city: Austin
    state: TX
  roles:
    - admin
    - user

Read, modify, and write the data:

from pathlib import Path
 
import yaml
 
input_path = Path("user.yaml")
output_path = Path("user-updated.yaml")
 
with input_path.open(encoding="utf-8") as file:
    data = yaml.safe_load(file)
 
for role in data["user"]["roles"]:
    print(role)
 
data["user"]["location"]["city"] = "Dallas"
 
with output_path.open("w", encoding="utf-8") as file:
    yaml.safe_dump(
        data,
        file,
        sort_keys=False,
        allow_unicode=True,
    )

Use safe_load for YAML received from a file, API, or user. PyYAML’s more permissive constructors can create arbitrary Python objects and are unsafe for untrusted input.

safe_dump emits standard YAML tags. It may change formatting and does not preserve comments from the original document. When round-trip preservation of comments, quoting, and layout matters, evaluate a round-trip-oriented library such as ruamel.yaml.

Note: YAML supports features and types beyond JSON. Do not assume that every YAML document will produce only dictionaries, lists, strings, numbers, booleans, and None.

XML with ElementTree

XML represents a tree of elements rather than native objects and arrays. xml.etree.ElementTree preserves that tree-shaped model.

Given user.xml:

<?xml version="1.0" encoding="UTF-8"?>
<root>
  <user>
    <name>john</name>
    <location>
      <city>Austin</city>
      <state>TX</state>
    </location>
    <roles>
      <role>admin</role>
      <role>user</role>
    </roles>
  </user>
</root>

Read, traverse, modify, and write the tree:

import xml.etree.ElementTree as ET
 
tree = ET.parse("user.xml")
root = tree.getroot()
 
user = root.find("user")
if user is None:
    raise ValueError("Missing <user> element")
 
for role in user.findall("./roles/role"):
    print(role.text)
 
city = user.find("./location/city")
if city is None:
    raise ValueError("Missing <city> element")
 
city.text = "Dallas"
 
tree.write(
    "user-updated.xml",
    encoding="utf-8",
    xml_declaration=True,
)

The main operations are:

OperationElementTree API
Parse a fileET.parse(...)
Get the document roottree.getroot()
Find the first matching elementelement.find(...)
Find all matching elementselement.findall(...)
Read or replace element character dataelement.text
Serialize the tree to a filetree.write(...)

Real API XML often uses namespaces. In that case, element searches must include the namespace URI or a prefix map; the visible prefix alone is not the element’s full name.

Why XML Libraries Feel Different

XML libraries expose different models:

LibraryModel exposed to PythonInstallation
xml.etree.ElementTreeElement treeStandard library
xml.dom.minidomDocument Object Model nodesStandard library
xmltodictDictionary-like mapping chosen by the libraryThird-party
untangleAttribute-style object interfaceThird-party

Converting XML to a dictionary can be convenient, but XML has attributes, namespaces, repeated elements, mixed content, and ordering. A dictionary conversion needs library-specific rules for those features. ElementTree keeps the XML tree explicit.

Deserialization Is Only the First Check

Successful parsing means the representation is syntactically acceptable. It does not mean the content is safe or correct.

roles = data.get("user", {}).get("roles")
 
if not isinstance(roles, list):
    raise ValueError("user.roles must be a list")

For production code:

  • limit input sizes where possible
  • handle parse errors
  • validate required fields and data types
  • write to a new file when preserving the original matters
  • use atomic replacement when partially written output would be dangerous
  • follow the API’s schema rather than guessing field meanings

The Python json documentation warns that maliciously large or deeply nested input can consume considerable CPU and memory. XML and YAML parsers also require defensive limits when processing untrusted data.

What About pickle?

pickle is Python-specific serialization that can preserve more Python object structure than JSON, YAML, or XML. It is not a general API interchange format.

Never unpickle untrusted data. A crafted pickle can execute arbitrary code during deserialization.

TL;DR

  • JSON and XML support comes with Python; PyYAML is installed separately.
  • pip install, import, and deserialization are three different operations.
  • Use json.load and json.dump for files; use loads and dumps for strings.
  • Use yaml.safe_load and yaml.safe_dump for ordinary YAML data.
  • ElementTree keeps XML as a tree rather than forcing it into a dictionary.
  • In-memory changes do not affect the source file until the program writes output.
  • Parsing does not replace schema and business-rule validation.
  • Never use pickle with untrusted data.

Resources

Note: Examples and security guidance were checked against current Python and PyYAML documentation in July 2026.