BoundaryML/baml · error · JwtError
JSON error: {0}
Error message
JSON error: {0} What it means
JwtError::JsonError converts a serde_json::Error (via #[from]) when deserializing decoded JWT segments (header/claims) into structs fails. After base64 decoding, the bytes must be valid JSON matching the expected shape.
Source
Thrown at engine/baml-runtime/src/internal/wasm_jwt.rs:29
use base64::{
engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD},
Engine,
};
use js_sys::{Array, Object, Uint8Array};
use serde::{Deserialize, Serialize};
use serde_json::json;
use thiserror::Error;
use wasm_bindgen::JsValue;
use wasm_bindgen_futures::JsFuture;
use web_sys::{window, CryptoKey, SubtleCrypto};
#[derive(Error, Debug)]
pub enum JwtError {
#[error("JavaScript error: {0:?}")]
JsError(JsValue),
#[error("Base64 decode error: {0}")]
Base64Error(#[from] base64::DecodeError),
#[error("JSON error: {0}")]
JsonError(#[from] serde_json::Error),
#[error("Missing window object")]
NoWindow,
#[error("Missing crypto API")]
NoCrypto,
}
impl From<JsValue> for JwtError {
fn from(err: JsValue) -> Self {
JwtError::JsError(err)
}
}
pub async fn encode_jwt(
claims: &serde_json::Value,
private_key_pem: &str,
) -> Result<String, JwtError> {
// Extract the crypto.subtle APIView on GitHub (pinned to bd85ce9dee)
Solutions
- Deserialize into serde_json::Value first and inspect the actual claims structure.
- Make serde struct fields Option or add #[serde(default)] for claims that may be absent.
- Verify the token payload decodes to JSON (e.g. echo in a JWT debugger).
- Align struct field names with the token via #[serde(rename)] attributes.
Example fix
// before
struct Claims { sub: String, admin: bool }
// after
#[derive(Deserialize)]
struct Claims { sub: String, #[serde(default)] admin: Option<bool> } Defensive patterns
Strategy: type-guard
Validate before calling
const claims = JSON.parse(atobUrlSafe(payloadSegment));
if (typeof claims.sub !== "string") throw new Error("Missing sub claim"); Type guard
function hasRequiredClaims(c) {
return typeof c?.sub === "string" && typeof c?.exp === "number";
} Try / catch
match serde_json::from_slice::<Claims>(&payload) {
Ok(claims) => Ok(claims),
Err(e) => {
let raw: serde_json::Value = serde_json::from_slice(&payload)?;
eprintln!("Unexpected claims: {}", raw);
Err(e.into())
}
} Prevention
- Make optional claims Option<T> or #[serde(default)] in your claims struct.
- Inspect a sample token in a JWT debugger to align field names/types.
- Handle issuers that emit nonstandard claims defensively.
When it happens
Trigger: Decoding a JWT whose payload segment is not valid JSON, or whose claims lack fields required by the target serde struct (missing/renamed keys, wrong types).
Common situations: Token from a nonstandard issuer with unexpected claim names; using a struct requiring fields the token omits; decoding garbage bytes; expiry/claims type mismatches (string vs number).
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
- JSON serialization error: {0}
- Failed to deserialize value: {e}
- JavaScript error: {0:?}
- Base64 decode error: {0}
- Missing window object
AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12).
Data as JSON: /api/errors/25d9ed86d597e49e.
Report an issue: GitHub.