nautechsystems/nautilus_trader · error

canonical backtest result must be a JSON object

Error message

canonical backtest result must be a JSON object

What it means

`validate_document` (used by both `from_slice` and `from_state`) first requires the canonical backtest result document to be a JSON object at the top level. If the parsed value is an array, string, number, or null, this error is returned before any schema checks.

Source

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

                "total_orders": state.total_orders.to_string(),
                "total_positions": state.total_positions.to_string(),
                "trader_id": state.trader_id,
            },
            "schema": CANONICAL_SCHEMA,
            "statistics": canonical_statistics(state.statistics),
            "summary": state.summary,
        });

        canonicalize_document(&mut document)?;
        validate_document(&document)?;
        Ok(Self { document })
    }
}

fn validate_document(document: &Value) -> anyhow::Result<()> {
    let object = document
        .as_object()
        .ok_or_else(|| anyhow::anyhow!("canonical backtest result must be a JSON object"))?;
    anyhow::ensure!(
        object.get("schema").and_then(Value::as_str) == Some(CANONICAL_SCHEMA),
        "unsupported canonical backtest result schema"
    );
    let fields = [
        "accounts",
        "components",
        "diagnostics",
        "fills",
        "orders",
        "portfolio_snapshots",
        "position_snapshots",
        "positions",
        "run",
        "schema",
        "statistics",
        "summary",
    ];

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure the top-level value is a single JSON object containing `schema`, `accounts`, `components`, etc.
  2. Unwrap any enclosing array and pass the individual result object
  3. Regenerate the document with the library's result writer to guarantee the correct top-level shape
  4. Check the producer code — if it saved the wrong level of the structure, fix the serialization call

Example fix

// before
let bytes = br#"[{"schema":"..."}]"#; // array
BacktestResult::from_slice(bytes)?;
// after
let bytes = br#"{"schema":"...","accounts":[],...}"#; // object
BacktestResult::from_slice(bytes)?;
Defensive patterns

Strategy: type-guard

Validate before calling

import json
doc = json.load(open(path))
assert isinstance(doc, dict), "result must be a top-level JSON object"

Type guard

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

Try / catch

match BacktestResult::from_slice(&bytes) {
    Err(e) if e.to_string().contains("must be a JSON object") => {
        eprintln!("unexpected top-level JSON type: {e:#}");
    }
    Err(e) => return Err(e),
    Ok(r) => r,
}

Prevention

When it happens

Trigger: Calling `from_slice` with a JSON array (e.g. a list of results), a bare string/number, or null bytes serialized as JSON; calling `from_state` after serializing something other than the result object.

Common situations: Wrapping multiple results in a `[...]` array for batch runs; saving only the `results` inner value or a summary string instead of the whole document; a producer bug that serialized the wrong value.

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