nautechsystems/nautilus_trader · error

CustomData serialization failed: {e}

Error message

CustomData serialization failed: {e}

What it means

add_custom_data serializes a CustomData record to JSON bytes before writing it to the Redis-backed cache. If serde_json cannot serialize the record, the failure is wrapped in this anyhow error. It surfaces when the payload contains types with no JSON representation (e.g. non-string map keys) or internal serialization bugs.

Source

Thrown at crates/infrastructure/src/redis/cache.rs:508

    /// # Errors
    ///
    /// Returns an error if the command cannot be sent to the background task channel.
    pub fn insert(&self, key: String, payload: Option<Vec<Bytes>>) -> anyhow::Result<()> {
        let op = DatabaseCommand::new(DatabaseOperation::Insert, key, payload);
        match self.tx.send(op) {
            Ok(()) => Ok(()),
            Err(e) => anyhow::bail!("{FAILED_TX_CHANNEL}: {e}"),
        }
    }

    /// Stores custom data in Redis (key format: `custom:<ts_init_020>:<uuid>`, value: full JSON).
    ///
    /// # Errors
    ///
    /// Returns an error if serialization fails or the insert command cannot be sent.
    pub fn add_custom_data(&self, data: &CustomData) -> anyhow::Result<()> {
        let json_bytes = serde_json::to_vec(data)
            .map_err(|e| anyhow::anyhow!("CustomData serialization failed: {e}"))?;
        let ts_init = data.ts_init().as_u64();
        let key = format!(
            "{CUSTOM}{REDIS_DELIMITER}{:020}{REDIS_DELIMITER}{}",
            ts_init,
            UUID4::new()
        );
        self.insert(key, Some(vec![Bytes::from(json_bytes)]))
    }

    /// Sends an update command for `key` with optional `payload` to Redis via the background task.
    ///
    /// # Errors
    ///
    /// Returns an error if the command cannot be sent to the background task channel.
    pub fn update(&mut self, key: String, payload: Option<Vec<Bytes>>) -> anyhow::Result<()> {
        let op = DatabaseCommand::new(DatabaseOperation::Update, key, payload);
        match self.tx.send(op) {
            Ok(()) => Ok(()),

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Inspect the serde message in the error to find the field that cannot be serialized and change its type (e.g. use string keys in maps).
  2. Ensure the CustomData type derives Serialize and all nested types have JSON-compatible representations.
  3. Upgrade nautilus_trader if a recent version had a serialization bug affecting your data type.
  4. If the type cannot be JSON-serialized, convert it to a supported representation before passing to add_custom_data.

Example fix

// before
let data = MyData::new(my_map_with_int_keys);
cache.add_custom_data(&data)?; // fails: key must be a string
// after
let data = MyData::new(my_map_with_string_keys);
cache.add_custom_data(&data)?;
Defensive patterns

Strategy: validation

Validate before calling

let json = serde_json::to_vec(&data).map_err(|e| format!("custom data not JSON-serializable: {e}"))?;

Type guard

fn is_json_serializable<T: serde::Serialize>(v: &T) -> bool { serde_json::to_vec(v).is_ok() }

Prevention

When it happens

Trigger: Calling add_custom_data (directly or via the Python wrapper py_add_custom_data) with a CustomData whose serde serialization fails — typically data containing maps with non-string keys or unsupported field types.

Common situations: Users feed custom market data or metadata into the cache containing structures serde_json cannot encode; usually introduced by a custom Data subtype with exotic field types added in a recent change.

Understand the failure class

Background: json.Marshal / "failed to marshal" errors in Go: why "unsupported type" happens and how to fix it — this error's family across 22 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/fcfce0814d09c35d. Report an issue: GitHub.