Comparison to alternatives

CX is one of many serialization / config / wire formats. This section is the honest side-by-side: what CX brings, where the incumbents are better, and which workloads should pick which tool. Each comparison covers feature parity, performance, ecosystem, learning curve, schema, identity / hash, and round-trip lossiness, with a closing "when to pick X" sentence.

When to use CX

The decision tree. Top-to-bottom, first match wins:

  • **You need one syntax for data and code** (config that is also a program, templates that are also data, a schema that is also a value). CX is the only format in this comparison that is homoiconic; pick CX.
  • **You need content-addressable identity** across languages and platforms (cache keys, dedup, signed documents, distributed ledgers). The CX SHA-256 of canonical bytes is the most boring possible implementation; pick CX.
  • **You round-trip between JSON / YAML / TOML / XML / Markdown** and lose information each time. CX is the superset that preserves all of them losslessly; pick CX.
  • **Your workload is bulk analytical scan on tabular data** at terabyte scale, exchanged with the broader ecosystem (DuckDB / Spark / pandas). Use **Parquet** for storage, **Arrow** for in-memory, optionally with CX as the metadata / config layer.
  • **Your workload is wire-format throughput between microservices** with a fixed schema, and you control both ends. Use **Protocol Buffers** or **MessagePack** — they will be smaller and faster on the wire than any text format.
  • **You need a single-file human-edited config** with no programmability and no cross-system identity requirements. **TOML** (Cargo, pyproject) or **YAML** (Kubernetes, CI) are everywhere; the ecosystem advantage is real.
  • **You need a typed, programmable config language** with a strong type system. **Dhall** or **Pkl** are good fits if the type-system rigor matters more than the homoiconic data-and-code unification.
  • **Otherwise** (general data / config / interchange with mixed workloads, mid-size documents, multiple consumers in different languages): CX is the all-purpose default.

CX vs JSON

JSON is the universal interchange baseline. CX is a superset projection — anything that round-trips through JSON also round-trips through CX. The interesting comparisons are where CX has features JSON does not, and where JSON has ecosystem JSON does not.

dimension json cx
Schema JSON Schema (separate spec) .cxs — same syntax as data (see schema)
Identity / hash No canonical form; hash drifts on key reorder SHA-256 of canonical bytes; stable across platforms (see hash)
Comments None (in the standard); JSON5 / JSONC are extensions [# … #] preserved by cx fmt, stripped by cx canonical
Trailing commas Forbidden Not applicable — bracket form, no commas in attribute list
Number precision Underspecified; JS doubles by default Sized type tags (:i64, :u32, :f32) (see sized-types)
Multi-line strings Escaped only Triple-quoted, whitespace-preserving (see strings)
Date / datetime String only First-class :date / :datetime scalar (see dates)
Bytes Base64 in a string First-class :bytes scalar (see bytes)
Tooling ecosystem Vast — every language, every editor, every CDN Tier-1 bindings V/Python/Go/Rust; LSP + tree-sitter + VS Code; growing
Wire size Baseline Comparable text form; data-bin / CXCol smaller
Parse speed Highly optimized parsers everywhere Reference parser ~600 MB/s on the gate-15 workload
Learning curve Trivial Bracket-and-sigil form; ~one hour for the data subset

**When to pick JSON instead of CX.** Your consumers are browser JavaScript, a CDN-served config endpoint, or any interface where you cannot install a CX parser. JSON remains the right wire-format choice for ubiquity alone.

**When to pick CX instead of JSON.** You want identity, schema, comments, mixed content, dates, or bytes without layering JSON Schema + JSON5 + base64 + custom date string conventions on top of JSON. CX gives you the whole stack in one syntax.

CX vs YAML

YAML optimises for human readability of multi-line strings and config-as-code. The cost is the famously large surface area (indentation sensitivity, tag system, anchor / alias / merge keys, the Norway problem).

dimension yaml cx
Indentation Semantic (whitespace-sensitive) Insignificant — bracket-structured
Anchors and aliases &anchor / *alias / << merge keys &anchor / *merge sigils — same idea, no separate merge keyword (see anchor / merge)
Multiline strings | (literal) and > (folded) Triple-quoted (always literal)
Norway problem no parses as false in YAML 1.1 Only true/false are bools; no /yes is a string
Schema JSON Schema (separate) .cxs (same syntax)
Identity / hash No canonical form SHA-256 of canonical bytes (see hash)
Comments # line comments only [# … #] nest, span lines, preserved by cx fmt
Tooling Kubernetes, GitLab CI, Ansible Growing — LSP + tree-sitter + VS Code; analytical pipelines
Round-trip Lossy in many YAML 1.1 → 1.2 cases Lossless by spec (see roundtrip)

**When to pick YAML.** Your config consumers are Kubernetes manifests, Ansible playbooks, GitHub Actions workflows, or any tool whose schema is in YAML. The ecosystem advantage is decisive.

**When to pick CX.** Anywhere you would have wanted YAML minus the indentation footguns, the Norway problem, and the lossy round-trip. The triple-quoted multiline string is as readable as YAML literal blocks without the indentation rules.

CX vs TOML

TOML's pitch is "obviously correct config for configuration files." It excels at flat key-value configs (Cargo.toml, pyproject.toml) and struggles with deep nesting.

dimension toml cx
Nesting Dotted keys / [[arrays of tables]] — awkward past 2-3 levels Native — every element nests
Schema No standard schema language .cxs (see schema)
Identity / hash No canonical form SHA-256 of canonical bytes
Tooling Cargo, pip, Poetry — narrow but deep LSP + tree-sitter; broader workloads than configs alone
Round-trip Lossy — comment placement and key order Lossless
Mixed content Not supported Native (prose form, see prose)
Date / datetime Native :datetime Native :date / :datetime / :time

**When to pick TOML.** Your config is flat — language toolchain manifests, Cargo dependencies, app configuration with two levels of nesting. TOML reads beautifully at that scale.

**When to pick CX.** Your config is genuinely hierarchical, you need cross-config identity, or the same documents will also be processed as data (e.g. shipped as configuration *and* hashed for a release manifest). CX scales up where TOML hits its ceiling.

CX vs XML

XML is CX's closest structural cousin — both are tree-shaped, both support attributes and mixed content, both have namespaces. The differences are syntactic density and the surrounding ecosystem (XSLT, XPath, XSD).

dimension xml cx
Syntax verbosity Open + close tag pair Single bracket pair
Attribute syntax name="value" inside open tag name=value inside bracket (see attributes)
Mixed content Native Native (see prose)
Namespaces xmlns:prefix=URI xmlns:prefix=URI (XML-aligned, see namespaces)
Selector XPath 3.1 CXPath — XPath 3.1 aligned (see cxpath)
Schema XSD (large, complex) or RELAX NG .cxs (CX syntax, small)
Identity / hash C14N (XML Canonicalization) cx canonical + SHA-256
Templating XSLT (separate language) [?for] / [?modify] — same language as data
Tooling Vast in enterprise / publishing; less in modern web Growing; same ecosystem reach is the long-term goal
Learning curve Familiar to a generation of developers Lower if XML is new; comparable if XML is known

**When to pick XML.** You are working in publishing / document workflows (DITA, DocBook, SVG), or your consumers are XML-native (SOAP, XHTML, RDF/XML). The tooling depth is unmatched in those niches.

**When to pick CX.** You want the XML data model (attributes, mixed content, namespaces, XPath) without the syntactic verbosity, without XSD, and with code-as- data unification. CX is what XML would look like if designed today.

CX vs Protocol Buffers

Protobuf is the schema-first binary wire format for service-to-service traffic. It is the right tool when you control both ends, the schema rarely changes, and every byte on the wire matters.

dimension protobuf cx
Wire format Binary — varint + length-prefix Text (CX), or data-bin / CXCol (binary)
Schema requirement Required — wire is unparseable without .proto Optional — value is self-describing
Wire size Typically smallest of any format here Comparable text size; data-bin closer to protobuf
Throughput Very high — compiled (de)serializers High but text-shaped; data-bin closes the gap
Schema evolution Tag-numbered fields — strong forward/backward compat Schema modes (open/strict/closed); attribute order is data-only
Tooling protoc + every major language Tier-1 V/Python/Go/Rust; growing
Human readability None — binary opaque Text-first; binary is an optimization
Identity / hash Wire bytes — stable iff schema is fixed Canonical bytes — stable regardless of schema

**When to pick Protobuf.** You have a controlled RPC system, the schema is well-understood, and the bandwidth / CPU savings are real (mobile, embedded, hot-path services). Honest concession: Protobuf wins on wire throughput in the steady state.

**When to pick CX.** Your data is also config, also logs, also human-edited at the boundary, also stored on disk for later inspection. Protobuf loses to text formats outside the wire-only path.

CX vs MessagePack

MessagePack is "binary JSON" — schemaless, more compact than text JSON, slightly slower than protobuf because it carries kind tags.

dimension msgpack cx
Wire format Binary, schemaless Text (CX) or binary (data-bin)
Schema None .cxs (optional)
Wire size Smaller than JSON; comparable to data-bin data-bin similar; CXCol smaller for tabular
Type richness Scalars + map + array; ext-type extension Eight scalar kinds, elements with attributes, mixed content, namespaces
Identity / hash Wire bytes — stable iff serializer is deterministic Canonical bytes — stable by construction
Tooling Wide language coverage Tier-1 V/Python/Go/Rust

**When to pick MessagePack.** You want JSON semantics on the wire without JSON wire size, and your data is flat / tabular / homogeneous. MessagePack is excellent at being "JSON minus the text.".

**When to pick CX.** Your data has shape — attributes, mixed content, namespaces, hierarchical structure that MessagePack would force into nested maps. data-bin covers the wire-size argument without giving up the shape.

CX vs Apache Pkl

Pkl is Apple's configuration language — declarative, strongly typed, programmable. The closest "config that is also code" peer to CX, with a different design centre.

dimension pkl cx
Surface Custom syntax — class-oriented Bracket form — homoiconic
Typing Strong, static, gradual Schema-validated (.cxs); type tags on scalars
Computation Lambdas, comprehensions, methods Directives — [?for], [?match], [?modify], [?let]
Output formats JSON / YAML / Plist / Properties + custom renderers CX / JSON / YAML / TOML / XML / Markdown / CSV / JSONL
Identity / hash Not built-in SHA-256 of canonical bytes
Module system Imports, packages, semantic versioning [?cx include] — capability-gated path resolution
Tooling LSP, IntelliJ plugin, CLI — solid LSP + tree-sitter + VS Code — comparable
Ecosystem Apple-backed; growing community Open-source; growing community

**When to pick Pkl.** You want a strongly typed config language with class-oriented composition and your consumers are JSON / YAML output. Pkl's type system is richer than the CX schema language.

**When to pick CX.** The same documents are also data to be hashed, queried, transformed, or shipped as payloads. Pkl is config-first; CX is data-and-code by design.

CX vs Dhall

Dhall is the typed-config purist's pick — total, non-Turing-complete, with a sound type system and normalised import semantics.

dimension dhall cx
Type system Total, sound, dependent-ish (System Fω) Schema validation; type tags on scalars
Computation λ-calculus — total, no recursion without bound Directives — [?for] / [?match] / [?modify]; bounded by resilience directives
Surface Haskell-flavoured ML syntax Bracket form
Output formats JSON / YAML / Text CX / JSON / YAML / TOML / XML / Markdown / CSV / JSONL
Imports Content-addressed by hash — same idea as CX [?cx include] with capability gating
Identity / hash Standard form hash for imports Canonical bytes + SHA-256 for any value
Learning curve Steep — ML / Haskell familiarity helps Moderate — bracket form is unfamiliar but small
Tooling LSP, type-aware; narrow LSP + tree-sitter + VS Code

**When to pick Dhall.** You want totality, soundness, and a small surface area as design goals; you are willing to pay for the steeper learning curve. Dhall is the right tool for safety-critical config.

**When to pick CX.** You want the content-addressed identity, the schema-validated config, and the programmable updates of Dhall — plus the ability to carry rich data (mixed content, columnar tables, dates, bytes) in the same syntax. Dhall is config; CX is config + data + code.

CX vs Parquet

Parquet is the columnar storage standard for analytical workloads — terabyte-scale scans, predicate pushdown, compression. CX is not Parquet's competitor; CXCol is, and Parquet is CX's export target.

dimension parquet cx-cxcol
Layout Column-oriented, on-disk Column-oriented (CXCol), in-memory; on-disk possible
Compression Snappy / Gzip / Zstd / LZ4 page-level Dictionary encoding on; compression optional
Schema Embedded — required for read .cxs schema or embedded type tags
Ecosystem Spark, DuckDB, Polars, pandas, Athena, BigQuery Native CX pipelines; Arrow bridge for everything else
Bridge CX ↔ Parquet bridge via Arrow (see parquet-bridge)
Identity / hash File hash — sensitive to write order Canonical bytes of the logical value
Streaming Row-group oriented Native streaming evaluator (see streaming)
Write speed ~150 MB/s on the reference benchmark Faster — no compression overhead by default

**When to pick Parquet.** Your data is large, tabular, queried by external tools (Spark / Athena / BigQuery / DuckDB), and stored on object storage. Parquet is the lingua franca.

**When to pick CXCol.** Your data is tabular but the producers and consumers are CX-native (pipelines, in- memory analytical jobs, services that ship columnar payloads). When you need to interchange with the broader ecosystem, export to Parquet.

CX vs Arrow

Arrow is the in-memory columnar standard. Like MessagePack, it is not a CX competitor — it is a layer CX integrates with.

dimension arrow cx-cxcol
Layout Column-oriented, in-memory Column-oriented, in-memory (compatible)
Wire format Flight (gRPC streaming) / IPC data-bin chunked tables
Interop pandas, Polars, DuckDB, Datafusion — zero-copy CX ↔ Arrow bridge (zero-copy, see arrow-bridge)
Schema Embedded .cxs or embedded
Identity / hash None standard Canonical bytes + SHA-256
Use case Cross-process / cross-language analytics Same plus document semantics

**When to pick Arrow.** You are doing pure analytics — no document semantics, no identity requirements, no schema as a value. Arrow is the right cross-engine in-memory layout.

**When to pick CXCol over Arrow.** You want Arrow's column-orientation plus CX's document semantics (mixed content rows, hierarchical headers, namespaced columns, hash-stable identity). Use the bridge — they share memory zero-copy.

Summary table

At a glance, the headline trade-offs for each alternative. **Pick CX** is the column that matters most for an evaluator question.

format strength weakness pick-cx-when
JSON Universal ecosystem No schema, no comments, no identity You need any of: schema / comments / identity / mixed content / dates / bytes
YAML Human-readable config Indentation footguns, lossy round-trip, Norway problem You want YAML readability without the footguns
TOML Flat config readability Awkward at depth, no schema Your config is hierarchical or grows beyond config
XML Tree-shaped, mixed content, XPath ecosystem Verbose, XSD complexity, no homoiconic code-as-data You want the XML data model in a denser syntax with code-as-data unified
Protobuf Wire size and throughput Schema-required, opaque without .proto, no document semantics Your data is also config / docs / hashed payloads, not just RPC wire
MessagePack Compact binary JSON Flat / no document semantics Your data has shape (attributes / mixed content / namespaces)
Pkl Strongly typed config language Config-only; no data identity, narrower output formats The same documents are data and code, not just config
Dhall Total, sound, content-addressed imports Steep learning curve, config-only You want Dhall semantics plus rich data shape and broad outputs
Parquet Analytical scan, ecosystem File-format only, no document semantics Producer and consumer are CX-native; export to Parquet for interchange
Arrow Zero-copy in-memory columnar No document semantics, no identity You want Arrow plus CX semantics (use the bridge — they coexist)

**The honest summary.** CX wins on **unification**: data + code + schema + identity in one syntax. It loses on **ecosystem maturity** — JSON, YAML, and Protobuf all have decades of library / editor / CDN depth. The question for an evaluator is whether the unification value exceeds the ecosystem-depth gap for your specific workload. For workloads in §13.1's "otherwise" bucket, the answer in 2026 is increasingly yes; for the niches above the "otherwise" line, pick the specialist.

If you have a workload that does not match any row of this table or §13.1's decision tree, file an issue at https://github.com/cx-home/cx — the comparison is maintained as a living document; new alternatives and new use-case scenarios are added as the ecosystem evolves.

At-a-glance feature matrix

One-screen feature matrix across CX, JSON, YAML, TOML, and XML. The per-format children (§13.2-§13.11) carry the depth; this matrix is the spreadsheet view.

| Feature | CX | JSON | YAML | TOML | XML | |---|---|---|---|---|---| | Syntax weight | brackets, no closing tags | curly braces + brackets | indent-significant | tables + key=val | open + close tags | | Strong types | ✅ int / float / bool / null / sized / decimal / bigint / date / datetime / bytes / atom | ❌ number only | partial (auto-detect) | ✅ int / float / bool / datetime | partial (xs:type) | | Comments | ✅ block and line | ❌ | ✅ # … | ✅ # … | ✅ | | Mixed content | ✅ first-class | ❌ | ❌ | ❌ | ✅ first-class | | Multiple top-level docs | ✅ no wrapper required | ❌ requires array | ✅ via --- separator | ❌ single document | partial | | Attribute / element distinction | ✅ explicit | ❌ flat keys | ❌ flat keys | ❌ flat keys | ✅ explicit | | Type fidelity through round-trip | ✅ via CXCol | ❌ int↔float coerced | partial | ✅ preserved | partial | | Tabular data efficiency | ✅ `:table` block, columnar binary | ❌ verbose array-of-objects | ❌ verbose | partial (array of tables) | ❌ verbose | | Streaming parser | ✅ pull-based handle API | partial | ❌ usually whole-file | ❌ | ✅ SAX | | Built-in query language | ✅ CXPath value kind, all 12 XPath 3.1 axes | ❌ third-party (jq) | ❌ third-party (yq) | ❌ | ✅ XPath 1.0 / XQuery | | Pure-functional transform | ✅ `[?modify]` focus + 11 actions, structural sharing | ❌ | ❌ | ❌ | partial (XSLT) | | Pattern-match dispatch | ✅ `[?match]` heterogeneous arms | ❌ | ❌ | ❌ | ❌ | | Module system | ✅ `[?def]` / `[?lib]` / cx.lock SRI | ❌ | ❌ | ❌ | partial (XInclude) |

Conversion-loss matrix

Lossless conversion means the data round-trips without semantic loss; presentation details (comments, whitespace, attribute order) may be normalized. The table below is the honest accounting of what survives, what gets normalized, and what is genuinely lossy.

CX → format → CX Lossless? What is preserved What is normalised or lost
CX → JSON → CX data ✅ / presentation 📋 Element shape, attributes, types via CXCol, values, nesting Comments dropped; element vs attribute distinction collapses to keys
CX → YAML → CX data ✅ / presentation 📋 Element shape, types via !!tags, multiline strings Comments dropped by most parsers; flow-vs-block style normalised
CX → TOML → CX data ✅ / presentation 📋 Element shape via table headers, strong types Comments dropped; section ordering normalised; deep nesting flattened
CX → XML → CX data ✅ / presentation 📋 Element shape, attributes, mixed content, namespaces, PIs Whitespace normalised per xml:space; entity references resolved
CX → CXCol → CX bytes ✅ Everything byte-for-byte Nothing — CXCol is the strict-canonical form
CX → CSV → CX data partial :table block columns and rows Non-table data not encodable; cell subtyping collapses

Presentation-layer differences — comments (only XML and CX preserve them; JSON has none; YAML and TOML lose them through most parsers), whitespace (normalised to the target conventions), attribute order (emission may pick a canonical order), anchor names (canonical form renumbers them). These all show up as identical hashes via cx hash because the hash operates on strict-canonical bytes, where presentation is stripped.

When CX wins

CX is the right pick for typed configuration, mixed content alongside structured data, lossless conversion across formats, cases where you need a stable canonical form for hashing or signing, and any pipeline where the query/transform layer (CXPath + [?match] + [?modify]) should live in the same syntax as the data.