BoundaryML/baml · error

Failed to deserialize value: {e}

Error message

Failed to deserialize value: {e}

What it means

ValueExpr::resolve_serde resolves an expression to a serde_json::Value and then deserializes it into an arbitrary DeserializeOwned type T. If serde_json::from_value fails — the resolved JSON shape does not match T's structure — the error is wrapped as 'Failed to deserialize value: {e}' with the serde detail appended.

Source

Thrown at engine/baml-lib/baml-types/src/value_expr.rs:585

        match self.resolve(ctx) {
            Ok(ResolvedValue::Numeric(n, ..)) => Ok(n),
            _ => Err(anyhow::anyhow!("Expected a numeric value")),
        }
    }

    pub fn resolve_null(&self, ctx: &impl GetEnvVar) -> Result<()> {
        match self.resolve(ctx) {
            Ok(ResolvedValue::Null(..)) => Ok(()),
            _ => Err(anyhow::anyhow!("Expected a null value")),
        }
    }

    pub fn resolve_serde<T: serde::de::DeserializeOwned>(&self, ctx: &impl GetEnvVar) -> Result<T> {
        let value = self.resolve(ctx)?;
        let value: serde_json::Value = value.try_into()?;
        match serde_json::from_value(value) {
            Ok(v) => Ok(v),
            Err(e) => Err(anyhow::anyhow!("Failed to deserialize value: {e}")),
        }
    }

    /// Resolve and deserialize, with support for template_string calls.
    pub fn resolve_serde_with_templates<T: serde::de::DeserializeOwned>(
        &self,
        ctx: &impl GetEnvVar,
        template_renderer: &impl TemplateStringRenderer,
    ) -> Result<T> {
        let value = self.resolve_with_templates(ctx, template_renderer)?;
        let value: serde_json::Value = value.try_into()?;
        match serde_json::from_value(value) {
            Ok(v) => Ok(v),
            Err(e) => Err(anyhow::anyhow!("Failed to deserialize value: {e}")),
        }
    }

    /// Resolve the value to a [`ResolvedValue`], with support for template_string calls.

View on GitHub (pinned to bd85ce9dee)

Solutions

  1. Read the serde detail after 'Failed to deserialize value:' — it names the exact field and expected type.
  2. Update the target struct T to match the resolved JSON (add #[serde(default)], Option, or rename attributes).
  3. Fix the config/expression so its shape matches T (correct types, no missing keys).
  4. Use resolve_serde_with_templates if the expression contains template_string parts that need rendering first.
  5. Inspect the intermediate serde_json::Value during development to see the actual shape.

Example fix

// before
#[derive(Deserialize)] struct Params { max_tokens: u32 } // value: {"max_tokens": "512"}
let p: Params = expr.resolve_serde(&ctx)?; // Failed to deserialize value: invalid type: string, expected u32
// after
// fix config: {"max_tokens": 512}  (or accept strings via a custom deserializer)
let p: Params = expr.resolve_serde(&ctx)?;
Defensive patterns

Strategy: try-catch

Validate before calling

let value: serde_json::Value = expr.resolve(&ctx)?.try_into()?;
serde_json::from_value::<T>(value.clone())
    .map_err(|e| anyhow!("shape mismatch for {}: {e}", std::any::type_name::<T>()))?;

Type guard

fn matches_shape<T: serde::de::DeserializeOwned>(v: &serde_json::Value) -> bool {
    serde_json::from_value::<T>(v.clone()).is_ok()
}

Try / catch

let parsed: T = expr.resolve_serde(&ctx)
    .map_err(|e| anyhow::anyhow!("config deserialization failed; check field types: {e:#}"))?;

Prevention

When it happens

Trigger: Calling resolve_serde::<T>() where the resolved JSON does not structurally match T: wrong field types (string where number required), missing required fields, arrays where structs expected, or null for a non-Option field.

Common situations: Deserializing LLM-provider parameters or user config into a Rust struct after renaming a struct field, changing a type from String to u64, or making an Option field required.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12). Data as JSON: /api/errors/1f1dffccb409581d. Report an issue: GitHub.