{"record":{"id":"0289b0d7d2581ab6","repo":"nautechsystems/nautilus_trader","slug":"serialized-enum-variant-was-not-an-object","errorCode":null,"errorMessage":"serialized enum variant was not an object","messagePattern":"serialized enum variant was not an object","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/backtest/src/result.rs","lineNumber":762,"sourceCode":"}\n\nfn patch_position_adjustment(\n    adjustment: &PositionAdjusted,\n    value: &mut Value,\n) -> anyhow::Result<()> {\n    let object = value\n        .as_object_mut()\n        .ok_or_else(|| anyhow::anyhow!(\"serialized position adjustment was not an object\"))?;\n    set_decimal(object, \"quantity_change\", adjustment.quantity_change);\n    Ok(())\n}\n\nfn variant_payload_mut(value: &mut Value) -> anyhow::Result<&mut Map<String, Value>> {\n    let variant = value\n        .as_object_mut()\n        .and_then(|object| object.values_mut().next())\n        .and_then(Value::as_object_mut)\n        .ok_or_else(|| anyhow::anyhow!(\"serialized enum variant was not an object\"))?;\n    Ok(variant)\n}\n\nfn canonical_value<T: Serialize>(source: &T) -> anyhow::Result<Value> {\n    let mut value = serde_json::to_value(source)?;\n    canonicalize_value(&mut value)?;\n    Ok(value)\n}\n\nfn canonicalize_value(value: &mut Value) -> anyhow::Result<()> {\n    value.sort_all_objects();\n    sort_named_arrays(value, false);\n    stringify_numbers(value)?;\n    Ok(())\n}\n\nfn canonicalize_document(value: &mut Value) -> anyhow::Result<()> {\n    value.sort_all_objects();","sourceCodeStart":744,"sourceCodeEnd":780,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/backtest/src/result.rs#L744-L780","documentation":"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`.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before: OrderFilled(Quantity)  // -> {\"OrderFilled\": 5}\n// after: OrderFilled { quantity: Quantity }  // -> {\"OrderFilled\": {\"quantity\": ...}}","handlingStrategy":"type-guard","validationCode":"let v = serde_json::to_value(&event)?;\nassert!(v.as_object().and_then(|o| o.values().next()).map(Value::is_object).unwrap_or(false));","typeGuard":"fn has_object_variant_payload(v: &serde_json::Value) -> bool {\n    v.as_object()\n        .and_then(|o| o.values().next())\n        .map(Value::is_object)\n        .unwrap_or(false)\n}","tryCatchPattern":null,"preventionTips":["Avoid tuple and unit variants in enums that feed result canonicalization.","Add a test iterating all event variants and asserting object payloads."],"tags":["serialization","json","enum"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}