clockworklabs/SpacetimeDB · error
heterogeneous array
Error message
heterogeneous array
What it means
Panic in SpacetimeDB's serde integration for `AlgebraicValue`. SatS arrays are strictly homogeneous; when a type is serialized through `SerializeArrayValue`, every element must serialize to the same primitive kind. `ArrayValueBuilder::push` enforces this and `expect("heterogeneous array")` fires when element N has a different value type than elements 0..N-1.
Source
Thrown at crates/sats/src/algebraic_value/ser.rs:248
}
/// Continuation for serializing an array.
pub struct SerializeArrayValue {
/// For efficiency, the first time `serialize_element` is done,
/// this is used to allocate with capacity.
len: Option<usize>,
/// The array being built.
array: ArrayValueBuilder,
}
impl ser::SerializeArray for SerializeArrayValue {
type Ok = AlgebraicValue;
type Error = <ValueSerializer as ser::Serializer>::Error;
fn serialize_element<T: ser::Serialize + ?Sized>(&mut self, elem: &T) -> Result<(), Self::Error> {
self.array
.push(value_serialize(elem), self.len.take())
.expect("heterogeneous array");
Ok(())
}
fn end(self) -> Result<Self::Ok, Self::Error> {
Ok(ArrayValue::from(self.array).into())
}
}
/// A builder for [`ArrayValue`]s
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
enum ArrayValueBuilder {
/// An array of [`SumValue`](crate::SumValue)s.
Sum(Vec<crate::SumValue>),
/// An array of [`ProductValue`](crate::ProductValue)s.
Product(Vec<crate::ProductValue>),
/// An array of [`bool`]s.
Bool(Vec<bool>),
/// An array of [`i8`]s.View on GitHub (pinned to 6dee26c6ef)
Solutions
- Normalize the data before serialization: map every element to one type (e.g. stringify scalars, or wrap in a sum type with uniform variants).
- Define a proper SatS schema element type (product or sum type) so all elements serialize identically, instead of relying on dynamic/any serialization.
- If consuming JSON, pre-validate the array with a check that all elements share one JSON kind before converting to `AlgebraicValue`.
- For custom Serialize impls, ensure element serialization is uniform — heterogeneous logic belongs in a sum type with named variants.
Example fix
// before: heterogeneous JSON array -> panics let v: AlgebraicValue = value_serialize(&serde_json::json!([1, "two", true])); // after: normalize all elements to one shape first let v: AlgebraicValue = value_serialize(&vec!["1", "two", "true"]); // homogeneous strings
Defensive patterns
Strategy: type-guard
Validate before calling
// Before serializing dynamic data, assert all elements share one value kind
fn homogeneous(vals: &[serde_json::Value]) -> bool {
match vals.split_first() {
Some((f, rest)) => rest.iter().all(|v| std::mem::discriminant(v) == std::mem::discriminant(f)),
None => true,
}
} Type guard
fn uniform_array(v: &serde_json::Value) -> Option<&Vec<serde_json::Value>> {
v.as_array().filter(|a| homogeneous(a))
} Try / catch
let r = std::panic::catch_unwind(AssertUnwindSafe(|| value_serialize(&data)));
match r { Ok(v) => v, Err(_) => return Err(SerializationError::heterogeneous_array) } Prevention
- Never store untyped JSON arrays directly; declare an explicit element type in the module schema.
- Normalize mixed-kind arrays (stringify, or wrap in a sum type) before conversion to AlgebraicValue.
- Unit-test serialization of every dynamic/dynamic-ish payload your module accepts.
When it happens
Trigger: Serializing a Rust sequence (Vec/array/struct with `deserialize_with`-style element visitors, or `serde_json::Value`-like dynamic data) into an `AlgebraicValue` where elements serialize to different kinds — e.g. first element serializes as an integer and the second as a string or a sum variant. Common with `serialize_any`-style serializers, enums with differing variant payloads, or serde_json `Value::Array` mixing number/string/bool.
Common situations: Storing JSON-ish dynamic data (`serde_json::Value`) in a SatS `array value` column; custom `Serialize` impls that emit different shapes per element; converting between formats where JSON allows heterogeneous arrays but SatS does not; RPC/subscription payloads round-tripping untyped JSON.
Related errors
- unable to decode args
- unable to encode view return value
- length didn't fit in `u32`
- Precheck failed: added sequence {sequence_name} has invalid
- Precheck failed: added sequence {sequence_name} has invalid
AI-assisted analysis of clockworklabs/SpacetimeDB@6dee26c6ef (2026-08-20).
Data as JSON: /api/errors/e7d26342736a2dfd.
Report an issue: GitHub.