{"record":{"id":"2c1a262e7be1760b","repo":"nautechsystems/nautilus_trader","slug":"canonical-result-summary-must-be-an-object","errorCode":null,"errorMessage":"canonical result summary must be an object","messagePattern":"canonical result summary must be an object","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/backtest/src/result.rs","lineNumber":349,"sourceCode":"        \"fills\",\n        \"orders\",\n        \"portfolio_snapshots\",\n        \"position_snapshots\",\n        \"positions\",\n    ] {\n        anyhow::ensure!(\n            object.get(key).is_some_and(Value::is_array),\n            \"canonical result field '{key}' must be an array\"\n        );\n    }\n    validate_components(object.get(\"components\").expect(\"validated field\"))?;\n    validate_diagnostics(object.get(\"diagnostics\").expect(\"validated field\"))?;\n    validate_run(object.get(\"run\").expect(\"validated field\"))?;\n    validate_statistics(object.get(\"statistics\").expect(\"validated field\"))?;\n    let summary = object\n        .get(\"summary\")\n        .and_then(Value::as_object)\n        .ok_or_else(|| anyhow::anyhow!(\"canonical result summary must be an object\"))?;\n    anyhow::ensure!(\n        summary.values().all(Value::is_string),\n        \"canonical result summary values must be strings\"\n    );\n    Ok(())\n}\n\nfn validate_fields(\n    object: &Map<String, Value>,\n    expected: &[&str],\n    context: &str,\n) -> anyhow::Result<()> {\n    let actual = object.keys().map(String::as_str).collect::<BTreeSet<_>>();\n    let expected = expected.iter().copied().collect::<BTreeSet<_>>();\n    anyhow::ensure!(\n        actual == expected,\n        \"{context} fields do not match the version 1 schema\"\n    );","sourceCodeStart":331,"sourceCodeEnd":367,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/backtest/src/result.rs#L331-L367","documentation":"`validate_document` requires the canonical result's `summary` field to be a JSON object whose values are all strings (it carries display/summary data). If `summary` is missing, not an object, or contains non-string values, this error is returned.","triggerScenarios":"Loading a document where `summary` is absent or is an array/scalar, or where a summary value is a number/bool/null/nested object (e.g. putting a numeric metric directly into summary instead of stringifying it).","commonSituations":"Custom writers adding numeric metrics to summary without string conversion; post-processing that inserts raw floats (NaN ratios, PnL) into summary; dropping summary during a partial export.","solutions":["Convert all summary values to strings (e.g. format floats with a fixed representation) before writing the document","Ensure `summary` exists and is an object even when empty: `{}`","Regenerate the result with the canonical writer rather than assembling summary by hand","Validate with a quick check: `all(isinstance(v, str) for v in doc[\"summary\"].values())`"],"exampleFix":"// before\n\"summary\": {\"pnl\": 1234.5}\n// after\n\"summary\": {\"pnl\": \"1234.5\"}","handlingStrategy":"type-guard","validationCode":"s = doc.get(\"summary\")\nassert isinstance(s, dict) and all(isinstance(v, str) for v in s.values()), \"summary must be object of strings\"","typeGuard":"fn has_valid_summary(doc: &serde_json::Value) -> bool {\n    doc.get(\"summary\").and_then(|v| v.as_object())\n       .is_some_and(|s| s.values().all(serde_json::Value::is_string))\n}","tryCatchPattern":"match BacktestResult::from_slice(&bytes) {\n    Err(e) if e.to_string().contains(\"summary must be an object\") => {\n        eprintln!(\"invalid summary section: {e:#}\");\n    }\n    Err(e) => return Err(e),\n    Ok(r) => r,\n}","preventionTips":["Stringify all summary values at write time with a stable format","Always emit `summary` as an object, `{}` if empty","Never insert raw floats/NaN into summary fields"],"tags":["json","schema","backtest","validation"],"backgroundTag":"schema-validation-failed","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}