Skip to content

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

Description

@kylecarow

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions