nautechsystems/nautilus_trader · error · anyhow::Error

Expected JSON array for contracts

Error message

Expected JSON array for contracts

What it means

`parse_contracts_from_json_array` parses a JSON string that must contain a top-level JSON array of contract objects. This error is thrown when the parsed value is a valid JSON but not an array (e.g. an object or scalar), so the subsequent per-element contract parsing cannot run.

Source

Thrown at crates/adapters/interactive_brokers/src/common/contracts.rs:168

        combo_legs_description: get_str("comboLegsDescrip"),
        combo_legs: Vec::new(),       // TODO: Parse combo_legs if needed
        delta_neutral_contract: None, // TODO: Parse delta_neutral_contract if needed
        issuer_id: get_str("issuerId"),
        description: get_str("description"),
    })
}

/// Parse multiple IB contracts from JSON array.
///
/// # Errors
///
/// Returns an error if the JSON string is invalid or if any contract fails to parse.
pub fn parse_contracts_from_json_array(json_str: &str) -> anyhow::Result<Vec<Contract>> {
    let value: Value = serde_json::from_str(json_str).context("Failed to parse JSON string")?;

    let array = value
        .as_array()
        .ok_or_else(|| anyhow::anyhow!("Expected JSON array for contracts"))?;

    let mut contracts = Vec::new();

    for (idx, item) in array.iter().enumerate() {
        match parse_contract_from_json(item) {
            Ok(contract) => contracts.push(contract),
            Err(e) => {
                tracing::warn!("Failed to parse contract at index {}: {}", idx, e);
            }
        }
    }

    Ok(contracts)
}

use anyhow::Context;

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure the JSON string's top level is an array: `[ {...}, {...} ]`.
  2. If the payload is wrapped in an object, extract the array field first before calling the parser.
  3. Verify the producer of the JSON (cache writer or Python bridge) serializes a `Vec<Contract>` / list, not a single contract.
  4. Regenerate or fix the corrupted cache file.

Example fix

// before
let contracts = parse_contracts_from_json_array(json_str)?;
// after
let root: serde_json::Value = serde_json::from_str(json_str)?;
let arr = if let Some(a) = root.as_array() {
    a.clone()
} else {
    vec![root] // treat single object as one-element list
};
let contracts = parse_contracts_from_json_array(&serde_json::to_string(&arr)?)?;
Defensive patterns

Strategy: type-guard

Validate before calling

let root: serde_json::Value = serde_json::from_str(json_str)?;
if !root.is_array() {
    return Err(anyhow::anyhow!("contracts payload must be a JSON array"));
}

Type guard

fn is_contract_array(v: &serde_json::Value) -> bool {
    v.as_array().map_or(false, |a| a.iter().all(|e| e.is_object()))
}

Try / catch

match parse_contracts_from_json_array(&json_str) {
    Ok(contracts) => use(contracts),
    Err(e) if e.to_string().contains("Expected JSON array") => {
        // fall back: try parsing as single contract object
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Passing a JSON string whose root is an object (e.g. `{"contracts": [...]}`) or a bare value (e.g. `"[]"` is fine but `[]` wrapped as a string is not) to `parse_contracts_from_json_array`.

Common situations: Cache files written by a different tool storing contracts under an object wrapper; the Python side sending a single contract object instead of a list; version mismatch between producer and consumer of the serialized contract dump.

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