nautechsystems/nautilus_trader · error

canonical result summary values must be strings

Error message

canonical result summary values must be strings

What it means

During validation of a canonical backtest result document, the 'summary' entry must be a JSON object whose every value is a string. The library writes summaries as string-keyed/string-valued maps (produced via canonical_summary), so any non-string value (number, bool, null, array, nested object) means the document was hand-edited or produced by an incompatible writer. Thrown by validate_document, which runs both when a document is constructed (from_state) and when one is parsed (from_slice).

Source

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

        "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"
    );
    Ok(())

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Quote every value in the summary object so all values are JSON strings (convert numbers with .to_string() before writing)
  2. Regenerate the document with the library's own canonicalization path instead of hand-editing
  3. Check the schema field equals the current CANONICAL_SCHEMA version and re-run any migration tooling

Example fix

// before
"summary": {"total_pnl": 1250.5}
// after
"summary": {"total_pnl": "1250.5"}
Defensive patterns

Strategy: validation

Validate before calling

fn summary_values_are_strings(doc: &serde_json::Value) -> bool {
    doc.get("summary")
        .and_then(serde_json::Value::as_object)
        .map(|s| s.values().all(serde_json::Value::is_string))
        .unwrap_or(false)
}

Type guard

fn is_string_map(v: &serde_json::Value) -> bool {
    v.as_object().map_or(false, |o| o.values().all(|x| x.is_string()))
}

Try / catch

match CanonicalResult::from_slice(&bytes) {
    Ok(result) => /* use result */,
    Err(e) if e.to_string().contains("summary values must be strings") => /* re-quote summary values or regenerate document */,
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: Calling CanonicalResult::from_slice on JSON where summary contains a non-string value (e.g. "summary": {"total_pnl": 123} instead of "123"), or feeding a summary map with nested/null values into document construction.

Common situations: Hand-editing a result JSON file; generating the file with an older or external tool that writes numeric metrics unquoted; merging summaries from another schema version.

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/0e306a8c385288b3. Report an issue: GitHub.