nautechsystems/nautilus_trader · error

serialized position adjustment was not an object

Error message

serialized position adjustment was not an object

What it means

Raised by `patch_position_adjustment` when the JSON produced from a `PositionAdjusted` event is not a JSON object (`Value::Object`), so its `quantity_change` key cannot be patched with a stringified decimal. The canonicalizer expects every serialized position-adjustment event to deserialize into a map; array/string/number encodings indicate the event type's serde shape changed.

Source

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

    }
}

fn patch_order_event_decimals(event: &OrderEventAny, value: &mut Value) -> anyhow::Result<()> {
    if let OrderEventAny::Initialized(initialized) = event {
        let payload = variant_payload_mut(value)?;
        set_decimal(payload, "limit_offset", initialized.limit_offset);
        set_decimal(payload, "trailing_offset", initialized.trailing_offset);
    }
    Ok(())
}

fn patch_position_adjustment(
    adjustment: &PositionAdjusted,
    value: &mut Value,
) -> anyhow::Result<()> {
    let object = value
        .as_object_mut()
        .ok_or_else(|| anyhow::anyhow!("serialized position adjustment was not an object"))?;
    set_decimal(object, "quantity_change", adjustment.quantity_change);
    Ok(())
}

fn variant_payload_mut(value: &mut Value) -> anyhow::Result<&mut Map<String, Value>> {
    let variant = value
        .as_object_mut()
        .and_then(|object| object.values_mut().next())
        .and_then(Value::as_object_mut)
        .ok_or_else(|| anyhow::anyhow!("serialized enum variant was not an object"))?;
    Ok(variant)
}

fn canonical_value<T: Serialize>(source: &T) -> anyhow::Result<Value> {
    let mut value = serde_json::to_value(source)?;
    canonicalize_value(&mut value)?;
    Ok(value)
}

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Keep `PositionAdjusted` serialized as a JSON object (struct or externally-tagged struct variant), not a tuple/newtype variant.
  2. Inspect the produced JSON with `serde_json::to_string_pretty` to see the actual shape.
  3. Update `patch_position_adjustment` if the representation legitimately changed, adjusting how it descends into the variant payload (use `variant_payload_mut`).
  4. Rebuild and rerun backtest result tests to refresh golden fixtures.

Example fix

// before: PositionAdjusted(Quantity)  // serializes to a bare number
// after:
#[derive(Serialize)]
struct PositionAdjusted { quantity_change: Quantity }
Defensive patterns

Strategy: type-guard

Validate before calling

let v = serde_json::to_value(&event)?;
assert!(v.is_object(), "PositionAdjusted must serialize to an object");

Type guard

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

Prevention

When it happens

Trigger: Canonicalizing a `PositionAdjusted` event whose `serde_json::to_value` yields a non-object — e.g. a newtype/enum variant serialized as an array or tuple because the struct gained tuple representation, or a custom `Serialize` impl emitting a bare string.

Common situations: Domain event structs changed from struct variants to tuple variants in an enum; developers adding fields to `PositionAdjusted` while switching its serde representation; stale fixtures from an older schema.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


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