Serialization 01 ๐ Parsing and Manipulating YAML and TOML Programmatically
YAML and TOML are declarative formats; the work of reading, modifying, and writing them happens in a host language. The parser you choose determines what data structure you get, how errors surface, and which specification version governs the file’s meaning. Python and JavaScript dominate the examples below because their ecosystems have mature, well-documented libraries for both formats, and because the patterns translate directly to other languages.
This chapter covers programmatic manipulation in Python and JavaScript: parsing YAML with PyYAML and js-yaml, parsing TOML with Python’s built-in tomllib and the js-toml package, the safety rules that prevent arbitrary code execution, and the round-trip problem that arises when you parse, modify, and write a file back. The sections that follow walk through YAML parsing and dumping, TOML parsing and writing, and the structural differences that determine which library and approach fits a given task.
Key point: Use safe_load for YAML and never load; use tomllib for reading TOML but a separate library for writing, because Python’s standard library parses but does not serialize.
Why programmatic manipulation matters
The safe-load problem. PyYAML’s yaml.load() can execute arbitrary Python code embedded in a YAML file through tags like !!python/object/apply. The documentation and every tutorial recommend yaml.safe_load() instead, which restricts the parser to standard YAML types and raises an exception on unsafe tags . The same distinction exists in JavaScript: js-yaml’s safeLoad (or load with the default safe schema) is the recommended path, and the unsafe schema should never touch untrusted input .
The read/write asymmetry problem. Python’s standard library added tomllib in Python 3.11, but it parses only. It does not write TOML. For writing, the documentation points to tomli-w for a simple dump API or tomlkit for style-preserving edits of existing files . This asymmetry is not a defect; it reflects a design decision that parsing is the common case and writing is rarer, so the standard library covers the common case and leaves writing to third-party packages.
The type mapping problem. YAML and TOML types do not map identically to host-language types. A TOML offset date-time becomes a Python datetime.datetime with a timezone; a local date-time becomes a datetime.datetime with tzinfo=None; a local date becomes datetime.date; a local time becomes datetime.time . YAML’s implicit typing means an unquoted 2026-05-05 may become a date object or a string depending on the parser and schema. Knowing the mapping is necessary before comparing or serializing parsed values.
a. Parsing and dumping YAML in Python
PyYAML is the standard YAML library for Python. Install it with pip install pyyaml. The two functions that matter are yaml.safe_load() for reading and yaml.dump() for writing .
import yaml
# Parse a YAML file into Python dicts and lists
with open("config.yaml", "r") as file:
config = yaml.safe_load(file)
# Access nested values as ordinary Python objects
host = config["database"]["host"]
port = config["database"]["port"]
# Modify the parsed data
config["database"]["port"] = 5433
config["features"] = ["auth", "billing"]
# Write back as block-style YAML
with open("config.yaml", "w") as file:
yaml.dump(config, file, default_flow_style=False, sort_keys=False)
default_flow_style=False forces block style, which is the readable indented form rather than inline braces. sort_keys=False preserves insertion order; without it, yaml.dump sorts keys alphabetically, which may change the file’s organization . For multi-document YAML streams, safe_load reads only the first document; yaml.safe_load_all() returns a generator over all documents.
The security rule is absolute: yaml.load() without a Loader argument is unsafe. yaml.safe_load() is the equivalent of yaml.load(file, Loader=yaml.SafeLoader) and rejects any tag that would construct a Python object .
b. Parsing and writing TOML in Python
Python 3.11 and later includes tomllib for parsing TOML 1.0.0 (and 3.15 adds TOML 1.1.0 support). The module has no write capability .
import tomllib
# Parse a TOML file: must be opened in binary mode
with open("pyproject.toml", "rb") as f:
data = tomllib.load(f)
# Parse a TOML string
toml_str = """
title = "My App"
[owner]
name = "Tom"
"""
data = tomllib.loads(toml_str)
# Access values
name = data["owner"]["name"]
The binary-mode requirement is specific to tomllib.load(): the first argument must be a readable binary file object. tomllib.loads() accepts a string directly . For writing TOML, the Python documentation recommends tomli-w for a simple dump API or tomlkit for edits that preserve comments and formatting in existing files . tomlkit is the correct choice when the file is hand-edited and comments must survive a programmatic change; tomli-w is the correct choice when generating a new file from a Python dictionary.
c. Parsing and dumping YAML and TOML in JavaScript
JavaScript has two well-maintained options: js-yaml for YAML and js-toml for TOML. Both support parsing and serialization, unlike Python’s asymmetric tomllib.
const yaml = require("js-yaml");
const fs = require("fs");
// Parse YAML: safeLoad rejects unsafe tags
const doc = yaml.safeLoad(fs.readFileSync("config.yml", "utf8"));
// Modify
doc.database.port = 5433;
// Serialize back to YAML
const output = yaml.dump(doc, { indent: 2 });
fs.writeFileSync("config.yml", output);
js-yaml implements YAML 1.2 and provides loadAll() for multi-document streams, which safeLoad does not handle . The dump options include indent for indentation width and sortKeys for deterministic output.
For TOML, js-toml parses to a JavaScript object and dumps back to a TOML string. It passes the official toml-test suite and supports TOML 1.1.0 parsing while emitting 1.0.0-compatible output .
import { load, dump } from "js-toml";
const data = load(`
title = "My App"
[owner]
name = "Tom"
`);
data.owner.name = "Ana";
const tomlString = dump(data);
The type mapping differs from Python: js-toml returns TomlDate and TomlTime subclasses of Date for the four TOML date/time types, preserving whether the source had an offset or was a local value .
Complete Example Session
# ============================================
# PART 1: YAML PARSE, MODIFY, WRITE (PYTHON)
# ============================================
import yaml
with open("app.yaml", "r") as f:
config = yaml.safe_load(f) # dicts and lists
config["server"]["port"] = 9090 # modify nested value
config["features"].append("metrics") # modify list
with open("app.yaml", "w") as f:
yaml.dump(
config,
f,
default_flow_style=False, # block style
sort_keys=False, # preserve order
)
# ============================================
# PART 2: YAML MULTI-DOCUMENT (PYTHON)
# ============================================
with open("bundle.yaml", "r") as f:
docs = list(yaml.safe_load_all(f)) # generator of dicts
# ============================================
# PART 3: TOML PARSE (PYTHON)
# ============================================
import tomllib
with open("pyproject.toml", "rb") as f: # binary mode
pyproject = tomllib.load(f)
deps = pyproject["project"]["dependencies"]
# ============================================
# PART 4: TOML WRITE (SEPARATE LIBRARY)
# ============================================
# pip install tomli-w
import tomli_w
with open("generated.toml", "wb") as f:
tomli_w.dump({"server": {"port": 8080}}, f)
# ============================================
# PART 5: YAML IN JAVASCRIPT
# ============================================
# const yaml = require("js-yaml");
# const doc = yaml.safeLoad(fs.readFileSync("app.yml", "utf8"));
# doc.server.port = 9090;
# fs.writeFileSync("app.yml", yaml.dump(doc));
# ============================================
# PART 6: TOML IN JAVASCRIPT
# ============================================
# import { load, dump } from "js-toml";
# const data = load("title = \"App\"");
# data.title = "New";
# const out = dump(data);
The parts covered YAML round-trip in Python, multi-document parsing, TOML parsing with tomllib, TOML writing with tomli-w, and the JavaScript equivalents for both formats.
Quick Reference
Python Libraries
| Format | Parse | Write | Notes |
|---|---|---|---|
| YAML | yaml.safe_load | yaml.dump | Never yaml.load |
| YAML multi-doc | yaml.safe_load_all | โ | Generator |
| TOML | tomllib.load (binary) | tomli_w.dump | Standard lib parses only |
| TOML edit-preserving | tomlkit | tomlkit | Keeps comments/formatting |
JavaScript Libraries
| Format | Parse | Write | Notes |
|---|---|---|---|
| YAML | yaml.safeLoad | yaml.dump | loadAll for multi-doc |
| TOML | js-toml.load | js-toml.dump | Both in one package |
TOML Type Mapping (Python tomllib)
| TOML Type | Python Type |
|---|---|
| Offset date-time | datetime with timezone |
| Local date-time | datetime with tzinfo=None |
| Local date | datetime.date |
| Local time | datetime.time |
| Array of tables | list[dict] |
Best Practices
โ Do This:
# Safe YAML parsing
config = yaml.safe_load(file) # โ
# Binary mode for tomllib
with open("f.toml", "rb") as f: tomllib.load(f) # โ
# Preserve order when writing YAML
yaml.dump(data, f, sort_keys=False) # โ
# Use tomlkit for files with comments
import tomlkit; doc = tomlkit.parse(text) # โ
โ Don’t Do This:
# Unsafe YAML load
config = yaml.load(file) # โ
# Text mode for tomllib.load
with open("f.toml", "r") as f: tomllib.load(f) # โ
# Expect tomllib to write
tomllib.dump(data) # โ (no such function)
# Ignore type mapping for dates
if data["date"] == "2026-05-05": ... # โ (it's a date object)
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Arbitrary code execution | yaml.load on untrusted input | yaml.safe_load |
tomllib.load fails on text mode | Requires binary file | Open with "rb" |
| YAML key order changes | sort_keys defaults to true | Pass sort_keys=False |
| Date comparison fails | TOML dates become Python objects | Compare with datetime.date |
| Comments lost on TOML write | tomli_w regenerates the file | Use tomlkit for edits |
Real-World Examples
1. Safe YAML load
config = yaml.safe_load(open("config.yaml"))
2. YAML dump with block style
yaml.dump(data, f, default_flow_style=False)
3. Multi-document YAML
docs = list(yaml.safe_load_all(f))
4. TOML binary load
with open("pyproject.toml", "rb") as f: data = tomllib.load(f)
5. TOML string load
data = tomllib.loads('name = "App"')
6. TOML write with tomli-w
import tomli_w; tomli_w.dump(data, open("out.toml", "wb"))
7. TOML comment-preserving edit
import tomlkit; doc = tomlkit.parse(text); doc["name"] = "New"
8. JS YAML safeLoad
const doc = yaml.safeLoad(fs.readFileSync("f.yml", "utf8"));
9. JS TOML load and dump
import { load, dump } from "js-toml";
10. Type check after TOML parse
from datetime import date; assert isinstance(data["created"], date)
Visual
The Python Library Split
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ PYTHON: READ AND WRITE โ
โ โ
โ YAML โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ PyYAML: safe_load (read) + dump (write) โ โ
โ โ One library, both directions โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ TOML โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ tomllib: load only (standard lib) โ โ
โ โ tomli-w: dump only (third party) โ โ
โ โ tomlkit: both, style-preserving โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ Reading is built in; writing requires a choice โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Safe vs Unsafe Loading
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ YAML LOADER SAFETY โ
โ โ
โ yaml.load(file) โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Parses any tag, including: โ โ
โ โ !!python/object/apply:os.system โ โ
โ โ โ executes arbitrary code โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ yaml.safe_load(file) โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Standard types only: โ โ
โ โ str, int, float, bool, null, list, dict โ โ
โ โ Unknown tags โ error, no execution โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Round-Trip Flow
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ PARSE, MODIFY, WRITE โ
โ โ
โ File on disk โ
โ (YAML or TOML text) โ
โ โ โ
โ โผ โ
โ Parser (safe_load / tomllib.load) โ
โ โ โ
โ โผ โ
โ Host-language object โ
โ (dict, list, datetime) โ
โ โ โ
โ โผ โ
โ Modify in memory โ
โ (dict[key] = value, append) โ
โ โ โ
โ โผ โ
โ Serializer (yaml.dump / tomli_w.dump) โ
โ โ โ
โ โผ โ
โ New file on disk โ
โ โ
โ Comments and formatting are NOT preserved โ
โ unless using a style-preserving library (tomlkit) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Type Mapping After Parse
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ TOML DATE/TIME IN PYTHON โ
โ โ
โ TOML source Python result โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโโ โ
โ 2026-05-05T10:00Z datetime (tz=timezone.utc) โ
โ 2026-05-05T10:00 datetime (tzinfo=None) โ
โ 2026-05-05 datetime.date โ
โ 10:00:00 datetime.time โ
โ โ
โ Compare against the correct type, not against strings โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| Python YAML parse | yaml.safe_load |
| Python YAML write | yaml.dump(sort_keys=False) |
| Python TOML parse | tomllib.load (binary mode) |
| Python TOML write | tomli_w or tomlkit |
| JS YAML parse | js-yaml.safeLoad |
| JS TOML parse/write | js-toml.load / dump |
| Unsafe function | yaml.load without Loader |
| TOML date mapping | Four types map to four Python types |
Key takeaways:
safe_loadis not optional.yaml.loadcan execute arbitrary code; every untrusted YAML file must go throughsafe_load, and js-yaml’s default schema rejects unsafe tags .- Python’s TOML story is asymmetric.
tomllibparses but does not write. Usetomli_wfor new files andtomlkitwhen comments and formatting must survive . - Binary mode is required for
tomllib.load. The function expects a binary file object, not a text-mode one . - Round-tripping loses formatting. A parse-dump cycle regenerates the file from the data structure; comments and key ordering disappear unless a style-preserving library is used.
- Type mapping matters after parsing. TOML date/times become host-language date objects, not strings. Comparing them to string literals fails .
Remember: Parsing is the easy half. The parser converts text to a data structure, and you work with that structure using the host language’s tools. The hard half is writing back without losing what the human put in the file: the comments, the key order, the quoting style. Use safe_load and tomllib to read, yaml.dump and tomli_w to write when generating new files, and tomlkit when editing files that people maintain by hand. The library choice is determined by whether the file is generated or curated.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!