ytsaurus-yson 0.2.0

YSON serializer and deserializer for YTsaurus (text and binary). Fork of ss123she/yson-rs @ ba2044c
Documentation

ytsaurus-yson

A YSON serializer and deserializer (text and binary) built on serde, for talking to YTsaurus.

This crate is a fork of ss123she/yson-rs, vendored at revision ba2044c711cefa65259e25122fea21c36f451093 (2026-04-01, published upstream as v0.1.3). Nearly all of the code is upstream's work. Everything this fork changed is listed in CHANGELOG.md.

The fork exists so that the job runtime can evolve the parser (streaming reads, in particular) without waiting on upstream. It is not published to crates.io, and the yson-rs name is not ours to claim.

Licensed under Apache-2.0.

Upstream offers yson-rs under either MIT or Apache-2.0; this fork takes it under Apache-2.0, which that dual offer permits. The upstream licence texts are retained here as received — see NOTICE for the full picture.

Usage

use serde::{Deserialize, Serialize};
use ytsaurus_yson::{YsonFormat, from_slice, to_vec};

#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct Row {
    key: String,
    count: u64,
}

let row = Row { key: "hello".into(), count: 42 };
let bytes = to_vec(&row, YsonFormat::Binary)?;
let decoded: Row = from_slice(&bytes, YsonFormat::Binary)?;
assert_eq!(row, decoded);
# Ok::<(), ytsaurus_yson::YsonError>(())

Attributes map to @-prefixed fields, and $value holds the body of an attributed scalar:

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct ControlRecord {
    #[serde(rename = "@table_index")]
    table_index: i64,
}
// YSON: <table_index=1>#

For columns holding arbitrary bytes rather than text, use serde_bytes — see the note below.

Binary format reference

The markers this crate implements, from the YSON spec:

Marker Type Payload
0x01 string zigzag varint length (sint32), then that many raw bytes
0x02 int64 zigzag varint (sint64)
0x03 double 8 bytes, little-endian
0x04 / 0x05 boolean false / true
0x06 uint64 unsigned varint
0x23 # entity
0x3C 0x3E < > attributes
0x5B 0x5D [ ] list
0x7B 0x7D { } map
0x3D = key/value separator
0x3B ; item separator

Testing

cargo test -p ytsaurus-yson      # 78 tests
cargo bench -p ytsaurus-yson     # see BENCHMARKS.md

Beyond upstream's suite, this fork adds golden-byte tests for every YTsaurus control record, round trips against fixtures generated by the Go YSON implementation, and a deterministic no-panic sweep. Performance baseline: BENCHMARKS.md.

The libFuzzer targets in fuzz/ need nightly and a separate install:

cargo install cargo-fuzz
cargo +nightly fuzz run fuzz_target_1 -- -max_total_time=60   # binary
cargo +nightly fuzz run fuzz_target_2 -- -max_total_time=60   # text

tests/fuzz_smoke_tests.rs covers the same entry points deterministically so that CI still gets a no-panic signal without them.

Known limitations

Things worth knowing before relying on this crate. None of them are known to be wrong for the YTsaurus job protocol, but several would bite outside it.

  1. The API takes a whole slice. from_slice needs the entire document in memory. Streaming a job's input — which can be far larger than RAM — is the job of ytsaurus-job, which reparses at record boundaries.

  2. Maps do not preserve key order. YsonNode::Map is a BTreeMap, so decoding and re-encoding sorts the keys. Values round-trip exactly; bytes do not. YTsaurus does not attach meaning to column order, so this is safe for rows, but it means you cannot use this type for byte-exact pass-through — use the raw-bytes path in ytsaurus-job for that.

  3. String is preferred over bytes when decoding. A YSON string that happens to be valid UTF-8 is offered to serde as a str; only invalid UTF-8 is offered as bytes. Decoding into String therefore fails on non-UTF-8 columns. For byte columns declare the field as #[serde(with = "serde_bytes")] Vec<u8>, which accepts both. YsonValue always keeps the raw bytes.

  4. Text-mode doubles lose precision. Serialising to text goes through a shortest-representation formatter, and YTsaurus documents this as lossy in general. Use binary for anything numeric you care about — which is what jobs use anyway.

  5. Recursion is capped at depth 128, and the limit is not configurable. Deeper documents fail with Recursion limit exceeded rather than overflowing the stack. Table rows never come close.

  6. YsonValue's Index impl panics on a missing key or a non-map value. Prefer attr() / as_str() / as_i64(), which return Option.

  7. Attribute keys collide with @-prefixed field names. Attributes are surfaced as fields named @name, so a map that genuinely contains the key @name is indistinguishable from an attribute called name. YTsaurus does not produce such column names.

  8. No i128/u128 support. YSON has no such type; values outside int64/uint64 are rejected at serialisation time.