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.