windmill-labs/windmill · error

Expected string, got {:?}

Error message

Expected string, got {:?}

What it means

A helper in windmill-store reads a stored value from the resource/variable store and expects the stored JSON to be a string. If the value in the store is any other JSON type (object, number, array, null), this error is thrown with a debug dump of the actual value.

Source

Thrown at backend/windmill-store/src/resources.rs:4118

    db: &DB,
    w_id: &str,
    s: String,
) -> std::result::Result<String, anyhow::Error> {
    use serde_json::Value;
    use windmill_common::db::DbWithOptAuthed;
    let value = Value::String(s);
    match transform_json_value(
        &DbWithOptAuthed::from_authed(authed, db.clone(), None),
        w_id,
        value,
        &None,
        None,
        0,
    )
    .await?
    {
        Value::String(s) => Ok(s),
        v => Err(anyhow::anyhow!("Expected string, got {:?}", v)),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;
    use std::sync::{Arc, Mutex};
    use windmill_common::audit::AuditAuthor;
    use windmill_common::db::DbWithOptAuthed;

    #[test]
    fn parse_symref_head_resolves_default_branch() {
        let out = "ref: refs/heads/main\tHEAD\n7ddb8cec9a0000000000000000000000000000aa\tHEAD\n";
        assert_eq!(
            parse_ls_remote_symref_head(out),
            (
                Some("main".to_string()),

View on GitHub (pinned to e474e8803c)

Solutions

  1. Inspect the stored value at that path (resource/variable page or DB) and confirm its actual JSON type
  2. Use the appropriate getter for the real type (JSON/object getter) or convert the value to a string
  3. Fix the resource/variable content so the value at that path is a plain string
  4. Update caller code to request the value with the matching typed API
  5. Check recent edits/migrations that could have changed the stored shape

Example fix

// before
let s = get_resource_value(...).await?; // fails if value is an object
// after
match get_value(...).await? {
    Value::String(s) => Ok(s),
    other => serde_json::from_value::<MyStruct>(other)
        .context("resource is structured, not a string"),
}
Defensive patterns

Strategy: type-guard

Validate before calling

// before calling the string getter, verify the stored shape
let stored: serde_json::Value = db
    .query_opt("SELECT value FROM resource WHERE path = $1", &[&path])?
    .ok_or_else(|| anyhow::anyhow!("resource {path} not found"))?
    .get(0);
if !stored.is_string() {
    return Err(anyhow::anyhow!("resource {path} is not a plain string"));
}

Type guard

fn is_json_string(v: &serde_json::Value) -> bool {
    matches!(v, serde_json::Value::String(_))
}

Try / catch

match get_value_as_string(db, &workspace, &path).await {
    Ok(s) => s,
    Err(e) if e.to_string().starts_with("Expected string, got") => {
        // value is structured: fetch as Value and extract/serialize
        let v = get_value(db, &workspace, &path).await?;
        serde_json::to_string(&v)?
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Fetching a resource/variable whose stored value is not a JSON string — e.g. the caller stored an object or number, or the path resolves to a sub-value inside a structured resource while the API contract expects a plain string.

Common situations: Storing a JSON object as a resource then reading it with a string-typed getter; a resource path pointing at a nested field whose type changed; migration or manual DB edits changing value shape; using variable endpoints on resources and vice versa.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/088f7cc4e0ce8e2d. Report an issue: GitHub.