Skip to content

Replace serde_ndim feature with per-call-site Nested wrapper (#32) - #33

Merged
kylecarow merged 2 commits into
mainfrom
serde-nested-format
Aug 7, 2026
Merged

Replace serde_ndim feature with per-call-site Nested wrapper (#32)#33
kylecarow merged 2 commits into
mainfrom
serde-nested-format

Conversation

@kylecarow

Copy link
Copy Markdown
Collaborator

Closes #32.

serde_ndim switched the write format for arrays globally via a Cargo feature. Since Cargo
features are additive and unify across the dependency graph, enabling it anywhere in a binary
silently flipped the wire format for every other ninterp consumer in that binary — no way to
opt in per call site. It was also broken for non-self-describing formats (bincode, postcard) in
both feature configurations: deserialize_any was called unconditionally, which those formats
don't support at all, and fixed-size grids ([ArrayBase<D, Ix1>; N]) serialize as a tuple, which
those formats encode without a length prefix, so reading it back as a seq desynchronized the byte
stream.

What changed

The ndarray format stays the derive default — fastest to parse, and the only format that works
with binary serializers. The nested format is now opt-in at the point of serialization:

// wrapping a value directly
serde_json::to_string(&Nested(&interp))?;

// on a field of your own type
#[derive(serde::Serialize)]
struct MyConfig {
    #[serde(serialize_with = "serialize_nested")]
    surface: Interp2DOwned<f64, strategy::Linear>,
}

Both are exposed via prelude, alongside everything else the crate re-exports for common use.

Reading is untouched and still accepts either format regardless of which one wrote it — this is
purely about what gets written. The tolerant reader (and the fixed-grid tuple/seq handling) is
now gated on Deserializer::is_human_readable(), so non-self-describing formats fall back to the
ndarray format instead of failing outright.

Testing

tests/serde_formats.rs round-trips every serializable type through both formats — the
SerializeNested impls are hand-written (serde has no channel to thread a format choice down a
value, so each level re-wraps its children), so this is what catches them drifting out of sync
with the derived Deserialize. Also covers: every InterpolatorEnum variant specifically (since
untagged enums fail confusingly when field names shift), Nested reaching through Vec/Option
containers, serialize_with on a field of an external struct, and a bincode round-trip as the
regression test for the two format-agnostic-reading bugs above.

cargo test --features serde / --no-default-features, cargo clippy --all-features --all-targets, cargo fmt --check, and cargo doc --all-features are all clean. All four
examples run; benches compile.

Breaking

serde_ndim no longer exists as a feature. Migrate to wrapping values in Nested (or
serialize_with = "serialize_nested" on a field) at the call site that wants the nested format.
Data written by prior versions still reads fine either way, since the reader was already
format-agnostic.

serde_ndim switched the write format for arrays globally via a Cargo feature,
which silently affects every other ninterp consumer in the same binary since
features are additive. It was also broken for non-self-describing formats
(bincode, postcard): deserialize_any is unconditional, and fixed-size grids
serialize as tuples, which desyncs the byte stream on read.

Replace it with a Nested wrapper / serialize_nested helper (exposed via
prelude) that opts into the nested-array format at the point of
serialization. The ndarray format stays the derive default: fastest, and the
only format that works with binary serializers. Reading already accepted
either format and continues to.

Gate the tolerant reader on is_human_readable() so non-self-describing
formats fall back to the ndarray format instead of failing, and fix the
fixed-size grid tuple/seq desync. tests/serde_formats.rs round-trips every
serializable type through both formats (catches the hand-written
SerializeNested impls drifting from derived Deserialize) plus a bincode
round-trip regression test.
@kylecarow
kylecarow merged commit 005f71a into main Aug 7, 2026
1 check passed
@kylecarow
kylecarow deleted the serde-nested-format branch August 7, 2026 06:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

serde: replace the serde_ndim feature with a per-call-site Nested wrapper

1 participant