I'm trying to deserialize JWT strings directly into nested Pydantic models, but I’m encountering a validation issue when passing the JWT string to a nested model field. Specifically, I’m using Pydantic's @model_validator (in "before" mode) to decode the JWT and merge its payload with the original values before validation.
The goal is to decode the JWT string, extract its payload, and then allow Pydantic to populate the corresponding fields in the nested model. However, when I use Pydantic's model_validate method to validate the model, it throws a ValidationError, stating that a string cannot be passed to a nested model.
Here is my code:
from typing import Dict, Any
import jwt
from pydantic import BaseModel, model_validator
class JwtDecodeError(Exception):
"""Custom exception for JWT decoding errors."""
pass
class JwtBaseModel(BaseModel):
session_jwt: str
@classmethod
@model_validator(mode="before")
def decode_jwt(cls, values: Dict[str, Any]) -> Dict[str, Any]:
"""Decodes the JWT token without requiring a signature."""
token = values.get("session_jwt")
if not token:
raise ValueError("JWT token is required")
try:
# Decode the JWT without signature verification
decoded_payload = jwt.decode(token, options={"verify_signature": False})
except jwt.DecodeError as e:
raise JwtDecodeError(f"Failed to decode JWT token: {str(e)}")
# Merge the decoded payload with values to pass it to the Pydantic model fields
values.update(decoded_payload)
return values
class UsernameSignature(JwtBaseModel):
valid: bool
busy: bool
exp: int
payment_required: bool
class Username(BaseModel):
busy: bool
meta: dict
payment_required: bool
signature: UsernameSignature
valid: bool
username: str
model_json = {
"busy": False,
"meta": {
"gameSessionId": "",
"tracing": None
},
"payment_required": True,
"signature": "eyJhbGciOiJSUzI1NiJ9.eyJ1c2VybmFtZSI6ImpkamRvd2RiIiwidmFsaWQiOnRydWUsImJ1c3kiOmZhbHNlLCJleHAiOjE3MjY3NzY1OTksInBheW1lbnRfcmVxdWlyZWQiOnRydWV9.VjCpW2rY8MEgcVKJrx9Igz05-OnPdJuZxmg4wFUuy25Ixik02O9BkqXoOrfdgo0QR_-BPzB1AdAoMoveu3eqVHwtKGOXzG__eU6kObOlcowqB7iu9SwfK6TXmh1djaxhCCOveOIcj3UOlXQIZ4b6JuFSkezzHAUfaBe2KZskhyFH107MPxLnwWnVL3jOJwOcM7ierd96Y1xyDqmBlL9k8hotTWHwsNlTi0H2GjXWdMk8c54eMGIBhma66q_S__LQ9k1VSZkIj1awOpPzBrUdxMNw9B6J1bsPN8ajLIrLM_oHrXWGDGpIK9Clmbi6XfPe87X09pKutRapKU5nCqOWog",
"username": "jdjdowdb",
"valid": True
}
parsed_model = Username.model_validate(model_json)
The error I get
pydantic_core._pydantic_core.ValidationError: 1 validation error for Username
signature
Input should be a valid dictionary or instance of UsernameSignature [type=model_type, input_value='eyJhbGciOiJSUzI1NiJ9.eyJ...e87X09pKutRapKU5nCqOWog', input_type=str]
For further information visit https://errors.pydantic.dev/2.9/v/model_type
Note that I had to modify your JWT decoding syntax a bit, you may be running a different version of JWT.
from typing import Any, Dict
from jwt import JWT
from jwt.exceptions import JWTDecodeError
from pydantic import BaseModel, model_validator
class JwtDecodeError(Exception):
"""Custom exception for JWT decoding errors."""
pass
class DeserializeJWT(BaseModel):
decoded_data: dict
@model_validator(mode="before")
@classmethod
def decode_jwt(cls, signature: str) -> Dict[str, Any]:
instance = JWT()
try:
decoded = instance.decode(signature, do_verify=False)
except JWTDecodeError as e:
raise JwtDecodeError(f"Failed to decode JWT token: {str(e)}")
return {"signature": signature, "decoded_data": decoded}
class Decoded(DeserializeJWT):
signature: str
class Username(BaseModel):
busy: bool
meta: dict
payment_required: bool
valid: bool
username: str
signature: Decoded
model_json = {
"busy": False,
"meta": {"gameSessionId": "", "tracing": None},
"payment_required": True,
"signature": "your_jwt",
"username": "jdjdowdb",
"valid": True,
}
parsed_model = Username.model_validate(model_json)