nautechsystems/nautilus_trader · error

canonical result statistics must be an object

Error message

canonical result statistics must be an object

What it means

`validate_statistics` checks the `statistics` section of a canonical backtest result. The whole section must be a JSON object; this error is thrown when it is instead an array, string, number, boolean, or null. The statistics block is expected to contain the sub-objects general/pnls/returns plus the returns_series array, so a non-object makes the document unusable.

Source

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

        return Ok(());
    }
    let value = value
        .as_str()
        .ok_or_else(|| anyhow::anyhow!("canonical run field '{field}' must be a decimal string"))?;
    anyhow::ensure!(
        value == "0"
            || (!value.is_empty()
                && !value.starts_with('0')
                && value.bytes().all(|byte| byte.is_ascii_digit())),
        "canonical run field '{field}' is not a canonical unsigned decimal"
    );
    Ok(())
}

fn validate_statistics(value: &Value) -> anyhow::Result<()> {
    let object = value
        .as_object()
        .ok_or_else(|| anyhow::anyhow!("canonical result statistics must be an object"))?;
    validate_fields(
        object,
        &["general", "pnls", "returns", "returns_series"],
        "canonical result statistics",
    )?;

    for key in ["general", "pnls", "returns"] {
        anyhow::ensure!(
            object.get(key).is_some_and(Value::is_object),
            "canonical statistics field '{key}' must be an object"
        );
    }
    anyhow::ensure!(
        object.get("returns_series").is_some_and(Value::is_array),
        "canonical returns series must be an array"
    );
    Ok(())
}

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure the statistics section is a JSON object with keys general, pnls, returns, and returns_series
  2. Regenerate the canonical result with the current library version
  3. Migrate/transform old-format statistics arrays into the object shape before validation
  4. Check that no post-processing step replaced or nulled the statistics field

Example fix

// before
{"statistics": []}
// after
{"statistics": {"general": {}, "pnls": {}, "returns": {}, "returns_series": []}}
Defensive patterns

Strategy: validation

Validate before calling

fn statistics_shape_is_valid(doc: &serde_json::Value) -> bool {
    doc.get("statistics").map_or(false, serde_json::Value::is_object)
}

Type guard

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

Try / catch

match validate_document(&doc) {
    Err(e) if e.to_string().contains("statistics must be an object") => {
        // rebuild statistics section as an object and retry
    }
    other => other,
}

Prevention

When it happens

Trigger: Passing a canonical result document to validate_document where `statistics` is not a JSON object — e.g. an empty array [], a null, or a string. Typically from a mismatched schema version or a document whose statistics section was replaced/corrupted.

Common situations: Old result formats where statistics was an array of metrics; documents assembled by hand where statistics was omitted and defaulted to null; a script that assigned the wrong container type when aggregating stats.

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