nautechsystems/nautilus_trader · error

Failed to serialize fill info

Error message

Failed to serialize fill info: {e}

What it means

insert_position_event serializes the optional OrderFilled.info field to a serde_json::Value before binding it into the position_event INSERT. If serde_json::to_value fails (e.g. a map key that is not a string, or a custom Serialize impl that errors), the error is wrapped with this message. This happens before any SQL runs, so nothing was written.

Solutions

  1. Inspect the serde error in the message; find the info field value whose serialization failed.
  2. Convert map keys to String before attaching the payload (e.g. HashMap<String, Value>, or .map(|(k,v)| (k.to_string(), v))).
  3. If info holds non-JSON-representable data, serialize it yourself with a lossy conversion (serde_json::to_value(&value).unwrap_or(Value::Null)) or drop it before emitting the event.
  4. Check the Serialize impl of any custom info type for fallible paths.

Example fix

// before: non-string map keys break serde_json
let info = HashMap::<Uuid, String>::from([(order_id, "ok".to_string())]);

// after: string keys serialize cleanly
let info = info.into_iter().map(|(k, v)| (k.to_string(), v)).collect::<HashMap<String, String>>();
Defensive patterns

Strategy: validation

Validate before calling

// ensure info serializes before handing the event to the cache
if let Some(info) = &event.info {
    serde_json::to_value(info).expect("info must be JSON-serializable: use string map keys");
}

Type guard

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

Try / catch

let info_json = event.info.as_ref()
    .map(serde_json::to_value)
    .transpose()
    .unwrap_or_else(|e| { log::warn!("info not serializable: {e}"); None });

Prevention

When it happens

Trigger: An OrderFilled whose info field is Some(value) where serializing that value fails — classically a JSON object built with non-string keys (serde_json requires string map keys) or a Serialize implementation returning Err.

Common situations: A strategy attaching an info payload built from a HashMap with non-string keys (e.g. Uuid or integer keys); a custom info type whose Serialize impl can fail (e.g. serializing a value that exceeds f64 range); using a serde_json version where non-string keys hard-error instead of being coerced.

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

Appendix: source

Thrown at crates/infrastructure/src/sql/queries.rs:1030

        sqlx::query(
            r#"
            INSERT INTO "trader" (id)
            VALUES ($1)
            ON CONFLICT (id) DO NOTHING
        "#,
        )
        .bind(event.trader_id.to_string())
        .execute(&mut **transaction)
        .await
        .map(|_| ())
        .map_err(|e| anyhow::anyhow!("Failed to insert into trader table: {e}"))?;

        let position_event_info = event
            .info
            .clone()
            .map(serde_json::to_value)
            .transpose()
            .map_err(|e| anyhow::anyhow!("Failed to serialize fill info: {e}"))?;

        sqlx::query(
            r#"
            INSERT INTO "position_event" (
                id, kind, trader_id, strategy_id, instrument_id, client_order_id, venue_order_id,
                account_id, trade_id, currency, order_type, order_side, last_px, last_qty,
                liquidity_side, position_id, commission, reconciliation, info, causation_id,
                ts_event, ts_init, created_at, updated_at
            ) VALUES (
                $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17,
                $18, $19, $20, $21, $22, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
            )
        "#,
        )
        .bind(event.event_id.to_string())
        .bind("OrderFilled")
        .bind(event.trader_id.to_string())
        .bind(event.strategy_id.to_string())

View on GitHub (pinned to 18893faf8b)