nautechsystems/nautilus_trader · error

canonical result components must be an object

Error message

canonical result components must be an object

What it means

The 'components' section of a canonical result must be a JSON object with keys actor_ids, exec_algorithm_ids, strategy_ids, and trader_state. This error is thrown when the value is null, an array, a string, or any non-object JSON value. It runs during validate_document for both parsed and constructed documents.

Source

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

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(())
}

fn validate_components(value: &Value) -> anyhow::Result<()> {
    let object = value
        .as_object()
        .ok_or_else(|| anyhow::anyhow!("canonical result components must be an object"))?;
    validate_fields(
        object,
        &[
            "actor_ids",
            "exec_algorithm_ids",
            "strategy_ids",
            "trader_state",
        ],
        "canonical result components",
    )?;

    for key in ["actor_ids", "exec_algorithm_ids", "strategy_ids"] {
        let values = object
            .get(key)
            .and_then(Value::as_array)
            .ok_or_else(|| anyhow::anyhow!("canonical component field '{key}' must be an array"))?;
        anyhow::ensure!(
            values.iter().all(Value::is_string),

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Change components to an object: {"actor_ids": [...], "exec_algorithm_ids": [...], "strategy_ids": [...], "trader_state": "..."}
  2. Check that the serialization code that builds 'components' emits a map, not a list
  3. Regenerate the document with the library's canonical writer

Example fix

// before
"components": []
// after
"components": {"actor_ids": [], "exec_algorithm_ids": [], "strategy_ids": ["S-1"], "trader_state": "..."}
Defensive patterns

Strategy: validation

Validate before calling

fn components_is_object(doc: &serde_json::Value) -> bool {
    doc.get("components").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("components must be an object") => /* convert components to an object and retry */,
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: from_slice on JSON with "components": [] or "components": null, or constructing a document whose components entry is not a JSON object.

Common situations: Hand-editing or programmatically assembling result JSON and serializing components as a list or omitting it to null; a tool from another schema emitting components as an array.

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