Summary
The workspace's serde_json declaration in Cargo.toml enables the arbitrary_precision feature:
serde_json = { version = "1.0.128", features = ["raw_value", "arbitrary_precision"] }
This feature is non-additive in practice. Cargo unifies features across the whole dependency graph, so any consumer of any spacetimedb crate (sdk, lib, sats, schema, …) ends up with arbitrary_precision enabled on their serde_json — even crates that have nothing to do with SpacetimeDB.
What breaks
With arbitrary_precision on, serde_json's parser emits every JSON number as a struct of the form:
{"$serde_json::private::Number": "..."}
…instead of via visit_f64 / visit_i64 / etc. Inside serde's internally-tagged-enum buffer-replay path (#[serde(tag = "...")], which buffers the entire JSON object into Content<'de> to dispatch on the discriminator), this means every numeric field becomes Content::Map(...) rather than Content::F64/I64/etc.
When the variant payload is then replayed into the field's expected type, deserialization fails with errors like:
invalid type: map, expected f64
(or i32/i64/u64 — whichever numeric field is encountered first).
Concrete impact
I'm a downstream user (Rust GUI app) that uses both spacetimedb-sdk and tdlib-rs (Telegram bindings). tdlib-rs makes heavy use of #[serde(tag = "@type")] enums for Telegram's discriminated-union wire format, and has many numeric (f64, i32, i53→i64) fields. Adding spacetimedb-sdk as a dep causes every getChat / searchChatMessages / etc. response to fail deserialization at the first numeric field reached through a tagged enum boundary.
A trimmed reproduction:
# Cargo.toml of any non-spacetimedb crate
[dependencies]
spacetimedb-sdk = "2" # or any spacetimedb crate
my-other-crate = "*" # uses #[serde(tag = "...")] + numeric fields
// my-other-crate fails at runtime even though its source didn't change
serde_json::from_str::<TaggedEnumWithNumericFields>(payload)
// -> Err("invalid type: map, expected f64")
Why it's a footgun
arbitrary_precision is a whole-program feature flag. Every crate that compiled cleanly without spacetimedb in the workspace can silently break when spacetimedb is added — no source change in the broken crate, no compile error, just runtime deserialization failures.
Per tracing of feature unification:
Without spacetimedb:
serde_json: ['alloc', 'default', 'indexmap', 'preserve_order', 'raw_value', 'std']
With spacetimedb-sdk added:
serde_json: ['alloc', 'arbitrary_precision', 'default', 'indexmap', 'preserve_order', 'raw_value', 'std']
^^^^^^^^^^^^^^^^^^^^^^^
This is similar in spirit to the serde_with base64-feature breakage and other non-additive features. The serde-rs/json maintainers warn against using arbitrary_precision in libraries for exactly this reason (serde-rs/json#852, #1033).
What I checked
I grepped the SpacetimeDB code for actual usages of arbitrary-precision-specific serde_json APIs — serde_json::Number::from_string_unchecked, big-decimal preservation patterns, Value-based pre-parse with custom Number handling — and didn't find any in crates/sats, crates/lib, crates/bindings, or crates/sdk. The only serde_json::Number usage is Number::from(3u8) in crates/cli/src/spacetime_config.rs, which doesn't require the feature. RawValue is used (separate raw_value feature, kept). Pretty-printing in CLI doesn't need it either.
So arbitrary_precision looks like a leftover feature that wasn't strictly required.
Fix
Drop the feature from the workspace declaration:
-serde_json = { version = "1.0.128", features = ["raw_value", "arbitrary_precision"] }
+serde_json = { version = "1.0.128", features = ["raw_value"] }
If a sub-crate genuinely needs arbitrary-precision number round-tripping (e.g. for U256/I256 wire decoding), opt it in locally rather than pushing it on every consumer's serde_json.
I've been running the diff locally for a few hours against tdlib-rs-heavy code paths with no functional regressions in the SpacetimeDB module I publish.
Workaround for downstream users
Until this lands upstream, downstream users can:
[patch.crates-io]
spacetimedb = { git = "...", branch = "no-arbitrary-precision" }
spacetimedb-sdk = { git = "...", branch = "no-arbitrary-precision" }
# … one entry per spacetimedb-* crate the workspace pulls in
…pointing at a fork with arbitrary_precision removed. (See https://github.com/fifteenlabs/SpacetimeDB/tree/fifteen/no-arbitrary-precision for an example.)
Happy to send a PR if useful.
Summary
The workspace's
serde_jsondeclaration inCargo.tomlenables thearbitrary_precisionfeature:This feature is non-additive in practice. Cargo unifies features across the whole dependency graph, so any consumer of any
spacetimedbcrate (sdk, lib, sats, schema, …) ends up witharbitrary_precisionenabled on theirserde_json— even crates that have nothing to do with SpacetimeDB.What breaks
With
arbitrary_precisionon, serde_json's parser emits every JSON number as a struct of the form:{"$serde_json::private::Number": "..."}…instead of via
visit_f64/visit_i64/ etc. Inside serde's internally-tagged-enum buffer-replay path (#[serde(tag = "...")], which buffers the entire JSON object intoContent<'de>to dispatch on the discriminator), this means every numeric field becomesContent::Map(...)rather thanContent::F64/I64/etc.When the variant payload is then replayed into the field's expected type, deserialization fails with errors like:
(or i32/i64/u64 — whichever numeric field is encountered first).
Concrete impact
I'm a downstream user (Rust GUI app) that uses both
spacetimedb-sdkandtdlib-rs(Telegram bindings).tdlib-rsmakes heavy use of#[serde(tag = "@type")]enums for Telegram's discriminated-union wire format, and has many numeric (f64,i32,i53→i64) fields. Addingspacetimedb-sdkas a dep causes everygetChat/searchChatMessages/ etc. response to fail deserialization at the first numeric field reached through a tagged enum boundary.A trimmed reproduction:
Why it's a footgun
arbitrary_precisionis a whole-program feature flag. Every crate that compiled cleanly without spacetimedb in the workspace can silently break when spacetimedb is added — no source change in the broken crate, no compile error, just runtime deserialization failures.Per
tracingof feature unification:This is similar in spirit to the serde_with
base64-feature breakage and other non-additive features. The serde-rs/json maintainers warn against usingarbitrary_precisionin libraries for exactly this reason (serde-rs/json#852, #1033).What I checked
I grepped the SpacetimeDB code for actual usages of arbitrary-precision-specific serde_json APIs —
serde_json::Number::from_string_unchecked, big-decimal preservation patterns,Value-based pre-parse with custom Number handling — and didn't find any incrates/sats,crates/lib,crates/bindings, orcrates/sdk. The onlyserde_json::Numberusage isNumber::from(3u8)incrates/cli/src/spacetime_config.rs, which doesn't require the feature.RawValueis used (separateraw_valuefeature, kept). Pretty-printing in CLI doesn't need it either.So
arbitrary_precisionlooks like a leftover feature that wasn't strictly required.Fix
Drop the feature from the workspace declaration:
If a sub-crate genuinely needs arbitrary-precision number round-tripping (e.g. for U256/I256 wire decoding), opt it in locally rather than pushing it on every consumer's
serde_json.I've been running the diff locally for a few hours against
tdlib-rs-heavy code paths with no functional regressions in the SpacetimeDB module I publish.Workaround for downstream users
Until this lands upstream, downstream users can:
…pointing at a fork with
arbitrary_precisionremoved. (See https://github.com/fifteenlabs/SpacetimeDB/tree/fifteen/no-arbitrary-precision for an example.)Happy to send a PR if useful.