nautechsystems/nautilus_trader · error

canonical result summary must be an object

Error message

canonical result summary must be an object

What it means

`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.

Source

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

        "fills",
        "orders",
        "portfolio_snapshots",
        "position_snapshots",
        "positions",
    ] {
        anyhow::ensure!(
            object.get(key).is_some_and(Value::is_array),
            "canonical result field '{key}' must be an array"
        );
    }
    validate_components(object.get("components").expect("validated field"))?;
    validate_diagnostics(object.get("diagnostics").expect("validated field"))?;
    validate_run(object.get("run").expect("validated field"))?;
    validate_statistics(object.get("statistics").expect("validated field"))?;
    let summary = object
        .get("summary")
        .and_then(Value::as_object)
        .ok_or_else(|| anyhow::anyhow!("canonical result summary must be an object"))?;
    anyhow::ensure!(
        summary.values().all(Value::is_string),
        "canonical result summary values must be strings"
    );
    Ok(())
}

fn validate_fields(
    object: &Map<String, Value>,
    expected: &[&str],
    context: &str,
) -> anyhow::Result<()> {
    let actual = object.keys().map(String::as_str).collect::<BTreeSet<_>>();
    let expected = expected.iter().copied().collect::<BTreeSet<_>>();
    anyhow::ensure!(
        actual == expected,
        "{context} fields do not match the version 1 schema"
    );

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Convert all summary values to strings (e.g. format floats with a fixed representation) before writing the document
  2. Ensure `summary` exists and is an object even when empty: `{}`
  3. Regenerate the result with the canonical writer rather than assembling summary by hand
  4. Validate with a quick check: `all(isinstance(v, str) for v in doc["summary"].values())`

Example fix

// before
"summary": {"pnl": 1234.5}
// after
"summary": {"pnl": "1234.5"}
Defensive patterns

Strategy: type-guard

Validate before calling

s = doc.get("summary")
assert isinstance(s, dict) and all(isinstance(v, str) for v in s.values()), "summary must be object of strings"

Type guard

fn has_valid_summary(doc: &serde_json::Value) -> bool {
    doc.get("summary").and_then(|v| v.as_object())
       .is_some_and(|s| s.values().all(serde_json::Value::is_string))
}

Try / catch

match BacktestResult::from_slice(&bytes) {
    Err(e) if e.to_string().contains("summary must be an object") => {
        eprintln!("invalid summary section: {e:#}");
    }
    Err(e) => return Err(e),
    Ok(r) => r,
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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