{"record":{"id":"c38ec48cf9d216ff","repo":"nautechsystems/nautilus_trader","slug":"serialized-position-was-not-an-object","errorCode":null,"errorMessage":"serialized position was not an object","messagePattern":"serialized position was not an object","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/backtest/src/result.rs","lineNumber":561,"sourceCode":"                continue;\n            }\n            let mut encoded = serde_json::to_value(event)?;\n            canonicalize_value(&mut encoded)?;\n            fills.push(json!({\n                \"client_order_id\": order.client_order_id().to_string(),\n                \"event\": encoded,\n                \"order_event_ordinal\": ordinal.to_string(),\n            }));\n        }\n    }\n    Ok(fills)\n}\n\nfn canonical_position(position: &Position) -> anyhow::Result<Value> {\n    let mut value = serde_json::to_value(position)?;\n    let object = value\n        .as_object_mut()\n        .ok_or_else(|| anyhow::anyhow!(\"serialized position was not an object\"))?;\n    anyhow::ensure!(\n        object.remove(\"id\").is_some(),\n        \"serialized position did not contain its identifier\"\n    );\n    object.insert(\n        \"position_id\".to_string(),\n        Value::String(position.id.to_string()),\n    );\n    set_f64(object, \"avg_px_close\", position.avg_px_close);\n    set_f64(object, \"avg_px_open\", Some(position.avg_px_open));\n    set_f64(object, \"realized_return\", Some(position.realized_return));\n    set_f64(object, \"signed_qty\", Some(position.signed_qty));\n\n    if let Some(adjustments) = object.get_mut(\"adjustments\").and_then(Value::as_array_mut) {\n        for (source, encoded) in position.adjustments.iter().zip(adjustments) {\n            patch_position_adjustment(source, encoded)?;\n        }\n    }","sourceCodeStart":543,"sourceCodeEnd":579,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/backtest/src/result.rs#L543-L579","documentation":"`canonical_position` serializes a Position to JSON and expects the top-level value to be a JSON object so it can rename `id` to `position_id` and patch decimal fields. This error is thrown when serde_json::to_value(position) yields a non-object (array, string, number, null), which cannot happen for the library's own Position struct — making this an internal invariant check against serialization drift or misuse.","triggerScenarios":"Canonicalizing a Position whose serde output is not a JSON object — e.g. the type was swapped for a wrapper/newtype, a custom Serialize impl returns a scalar, or a different Position type/version was passed in. Also triggered if serialization itself was intercepted and transformed.","commonSituations":"Version mismatch between crates producing and consuming Position serde formats; a custom Serialize/with-wrapper implementation around Position; passing a similarly-named type from another module into the result canonicalizer.","solutions":["Serialize the standard library Position type directly, with no wrapper or custom Serialize impl","Align crate versions so the Position serde format matches what canonical_position expects","Inspect serde_json::to_value(position) to confirm it yields a top-level object","Do not transform the serialized value before passing it through canonicalization"],"exampleFix":"// before (custom newtype serialization)\nstruct PosRef(u64); // serializes as a number\n// after\nserde_json::to_value(&position)? // standard Position -> top-level object","handlingStrategy":"try-catch","validationCode":"fn position_serializes_as_object(position: &Position) -> anyhow::Result<bool> {\n    Ok(serde_json::to_value(position)?.is_object())\n}","typeGuard":"fn is_json_object(v: &serde_json::Value) -> bool { v.is_object() }","tryCatchPattern":"let canonical = canonical_position(&position).map_err(|e| {\n    if e.to_string().contains(\"was not an object\") {\n        // inspect serde_json::to_value(position) and crate versions\n    }\n    e\n})?;","preventionTips":["Serialize the library's Position type directly, without wrappers","Keep Position serde derives unchanged and version-aligned across crates","Add a unit test asserting to_value(position) is an object with an `id` field","Avoid custom Serialize impls or intermediate transformations on Position"],"tags":["serde","internal","serialization","backtest"],"backgroundTag":"internal-invariant-violation","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"}