BoundaryML/baml · error

--json-args must be a JSON object, got: {json}

Error message

--json-args must be a JSON object, got: {json}

What it means

`--json-args` must parse to a JSON object (a map of parameter name to value). If the provided JSON is any other shape (array, string, number, boolean, null), `build_args_from_signature_with_context` rejects it, echoing the offending JSON. Arrays/strings are never auto-mapped positionally.

Source

Thrown at baml_language/crates/baml_exec/src/dispatch.rs:210

}

async fn build_args_from_signature_with_context(
    engine: &Arc<BexEngine>,
    cli_values: HashMap<String, BexExternalValue>,
    json_args: Option<&serde_json::Value>,
    param_names: &[String],
    param_types: &[RuntimeTy],
    param_has_default: &[bool],
    helper_context: &HelperCallContext,
) -> Result<Vec<BexCallArg>> {
    let _ = param_types;
    let mut merged: HashMap<String, RawArg> = HashMap::new();

    // `--json-args` first (lower priority — CLI overrides).
    if let Some(json) = json_args {
        let obj = json
            .as_object()
            .ok_or_else(|| anyhow!("--json-args must be a JSON object, got: {json}"))?;
        for (key, value) in obj {
            // Re-stringify each field so the engine deserializer takes a
            // fresh JSON document. Round-tripping via `to_string` is
            // cheaper than implementing a parallel json-value-to-engine
            // path; per-arg JSON is typically tiny.
            merged.insert(key.clone(), RawArg::JsonText(value.to_string()));
        }
    }

    // Auto-CLI flags override. By this point the clap parser has already
    // validated structure (required flags, unknown flags) and converted
    // each raw string to a typed primitive — structured types arrive via
    // `--json-args` only.
    for (key, value) in cli_values {
        merged.insert(key, RawArg::Primitive(Box::new(value)));
    }

    let mut ordered = Vec::with_capacity(param_names.len());

View on GitHub (pinned to bd85ce9dee)

Solutions

  1. Wrap the payload in a JSON object keyed by parameter names: `{"param": value}`.
  2. If the file contains an array, restructure it to an object or pass the array as the value of an object-typed parameter.
  3. Validate the file with jq (e.g. `jq type` should print `object`) before passing via `--json-args @file`.

Example fix

// before
baml run myFunc -- --json-args '["a", 1]'

// after
baml run myFunc -- --json-args '{"name": "a", "count": 1}'
Defensive patterns

Strategy: validation

Validate before calling

let json: serde_json::Value = serde_json::from_str(&raw)?;
if !json.is_object() {
    anyhow::bail!("--json-args must be an object of {param: value}");
}

Type guard

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

Try / catch

match result {
    Err(e) if e.to_string().contains("--json-args must be a JSON object") => {
        eprintln!("wrap the payload as an object keyed by parameter names");
    }
    other => other,
}

Prevention

When it happens

Trigger: Passing `--json-args '[1,2]'`, `--json-args '"hi"'`, or a file/stdin whose content is valid JSON but not an object; the check is `json.as_object()` in dispatch.rs:210.

Common situations: Feeding a top-level JSON array of arguments, forgetting `{}` braces, or pointing `@file` at a file that contains a JSON array instead of a parameter map.

Understand the failure class

Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.

Related errors


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