| | |

Serialization 01 ๐Ÿ“„ Parsing and Manipulating YAML and TOML Programmatically

โฑ๏ธ Estimated read: 10 min | |

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

FormatParseWriteNotes
YAMLyaml.safe_loadyaml.dumpNever yaml.load
YAML multi-docyaml.safe_load_allโ€”Generator
TOMLtomllib.load (binary)tomli_w.dumpStandard lib parses only
TOML edit-preservingtomlkittomlkitKeeps comments/formatting

JavaScript Libraries

FormatParseWriteNotes
YAMLyaml.safeLoadyaml.dumploadAll for multi-doc
TOMLjs-toml.loadjs-toml.dumpBoth in one package

TOML Type Mapping (Python tomllib)

TOML TypePython Type
Offset date-timedatetime with timezone
Local date-timedatetime with tzinfo=None
Local datedatetime.date
Local timedatetime.time
Array of tableslist[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

PitfallWhy It HappensFix
Arbitrary code executionyaml.load on untrusted inputyaml.safe_load
tomllib.load fails on text modeRequires binary fileOpen with "rb"
YAML key order changessort_keys defaults to truePass sort_keys=False
Date comparison failsTOML dates become Python objectsCompare with datetime.date
Comments lost on TOML writetomli_w regenerates the fileUse 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

ItemValue
Python YAML parseyaml.safe_load
Python YAML writeyaml.dump(sort_keys=False)
Python TOML parsetomllib.load (binary mode)
Python TOML writetomli_w or tomlkit
JS YAML parsejs-yaml.safeLoad
JS TOML parse/writejs-toml.load / dump
Unsafe functionyaml.load without Loader
TOML date mappingFour types map to four Python types

Key takeaways:

  • safe_load is not optional. yaml.load can execute arbitrary code; every untrusted YAML file must go through safe_load, and js-yaml’s default schema rejects unsafe tags .
  • Python’s TOML story is asymmetric. tomllib parses but does not write. Use tomli_w for new files and tomlkit when 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.

Was this helpful?


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!