nautechsystems/nautilus_trader · error
serialized enum variant was not an object
Error message
serialized enum variant was not an object
What it means
Raised by `variant_payload_mut`, which expects a serialized enum (externally tagged) as `{ "VariantName": { ...payload... } }`. It grabs the first value of the outer object and requires it to be an object; if the variant payload is an array, string, or number, the canonicalizer cannot patch decimals and fails. Callers include `canonical_order`, `canonical_position`, `canonical_account`, and `patch_order_event_decimals`.
Source
Thrown at crates/backtest/src/result.rs:762
}
fn patch_position_adjustment(
adjustment: &PositionAdjusted,
value: &mut Value,
) -> anyhow::Result<()> {
let object = value
.as_object_mut()
.ok_or_else(|| anyhow::anyhow!("serialized position adjustment was not an object"))?;
set_decimal(object, "quantity_change", adjustment.quantity_change);
Ok(())
}
fn variant_payload_mut(value: &mut Value) -> anyhow::Result<&mut Map<String, Value>> {
let variant = value
.as_object_mut()
.and_then(|object| object.values_mut().next())
.and_then(Value::as_object_mut)
.ok_or_else(|| anyhow::anyhow!("serialized enum variant was not an object"))?;
Ok(variant)
}
fn canonical_value<T: Serialize>(source: &T) -> anyhow::Result<Value> {
let mut value = serde_json::to_value(source)?;
canonicalize_value(&mut value)?;
Ok(value)
}
fn canonicalize_value(value: &mut Value) -> anyhow::Result<()> {
value.sort_all_objects();
sort_named_arrays(value, false);
stringify_numbers(value)?;
Ok(())
}
fn canonicalize_document(value: &mut Value) -> anyhow::Result<()> {
value.sort_all_objects();View on GitHub (pinned to 18893faf8b)
Solutions
- Use only struct variants (externally tagged with object payloads) in enums fed to canonicalization.
- If the variant is a newtype wrapping a struct, wrap it so the payload is `"Variant": { field: ... }`.
- Verify with `serde_json::to_value(&event)` that the top level is `{ "Name": { ... } }`.
- Extend `variant_payload_mut` to handle the new representation only if the schema change is intentional.
Example fix
// before: OrderFilled(Quantity) // -> {"OrderFilled": 5}
// after: OrderFilled { quantity: Quantity } // -> {"OrderFilled": {"quantity": ...}} Defensive patterns
Strategy: type-guard
Validate before calling
let v = serde_json::to_value(&event)?; assert!(v.as_object().and_then(|o| o.values().next()).map(Value::is_object).unwrap_or(false));
Type guard
fn has_object_variant_payload(v: &serde_json::Value) -> bool {
v.as_object()
.and_then(|o| o.values().next())
.map(Value::is_object)
.unwrap_or(false)
} Prevention
- Avoid tuple and unit variants in enums that feed result canonicalization.
- Add a test iterating all event variants and asserting object payloads.
When it happens
Trigger: Canonicalizing any order/position/account event enum whose variant payload serializes as a non-object — tuple variants, newtype variants wrapping primitives/arrays, or unit variants (which serialize to a bare string).
Common situations: Adding a new unit or tuple variant to an event enum used in backtest results; changing an enum from struct to tuple representation; deserializing an unexpected type into the canonicalizer.
Understand the failure class
Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.
Related errors
- Python object has no to_json() method or __dict__ attribute
- Failed to serialize the deployment manifest
- unsupported canonical diagnostic code
- serialized position adjustment was not an object
- Failed to serialize json `payload`: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/0289b0d7d2581ab6.
Report an issue: GitHub.