nautechsystems/nautilus_trader · error

canonical result run must be an object

Error message

canonical result run must be an object

What it means

The 'run' section of a canonical result must be a JSON object holding the run metadata (backtest_end_ns, backtest_start_ns, iterations, outcome, run_config_id, total_events, total_orders, total_positions, trader_id). validate_run throws this when 'run' is null, an array, or any other non-object type, during both from_state construction and from_slice parsing.

Source

Thrown at crates/backtest/src/result.rs:423

        .as_array()
        .ok_or_else(|| anyhow::anyhow!("canonical diagnostics must be an array"))?;
    for diagnostic in diagnostics {
        let object = diagnostic
            .as_object()
            .ok_or_else(|| anyhow::anyhow!("canonical diagnostic must be an object"))?;
        validate_fields(object, &["code"], "canonical diagnostic")?;
        anyhow::ensure!(
            object.get("code").and_then(Value::as_str) == Some("funding-settlement-failed"),
            "unsupported canonical diagnostic code"
        );
    }
    Ok(())
}

fn validate_run(value: &Value) -> anyhow::Result<()> {
    let object = value
        .as_object()
        .ok_or_else(|| anyhow::anyhow!("canonical result run must be an object"))?;
    validate_fields(
        object,
        &[
            "backtest_end_ns",
            "backtest_start_ns",
            "iterations",
            "outcome",
            "run_config_id",
            "total_events",
            "total_orders",
            "total_positions",
            "trader_id",
        ],
        "canonical result run",
    )?;

    for key in [
        "iterations",

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Change run to an object containing exactly the nine required schema fields
  2. Fix the serializer so run metadata is emitted as a JSON map
  3. Regenerate the document with the library's canonical writer

Example fix

// before
"run": []
// after
"run": {"backtest_end_ns": null, "backtest_start_ns": null, "iterations": "1", "outcome": "completed", "run_config_id": null, "total_events": "0", "total_orders": "0", "total_positions": "0", "trader_id": "TRADER-001"}
Defensive patterns

Strategy: validation

Validate before calling

fn run_is_object(doc: &serde_json::Value) -> bool {
    doc.get("run").map_or(false, |v| v.is_object())
}

Type guard

fn is_object(v: &serde_json::Value) -> bool { v.is_object() }

Try / catch

match CanonicalResult::from_slice(&bytes) {
    Ok(result) => /* use result */,
    Err(e) if e.to_string().contains("run must be an object") => /* rebuild 'run' as an object with the nine schema fields */,
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: from_slice with "run": [] or "run": null; constructing a document where the run metadata was serialized as a list or omitted to null.

Common situations: Hand-assembled result JSON with run as an array of key/value pairs; a producer from another schema nesting run differently.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/191b0358a42eca37. Report an issue: GitHub.