Mike Dedys
12/18/2025, 3:46 PMTask run 'google_download_file-c9c' - Encountered an error while serializing result for transaction 'ccda71d380b8bf741ee2e6fa41c9005a': Failed to serialize object of type 'APIResult[TypeVar]' with serializer 'json'
Is there a way to handle this?Marvin
12/18/2025, 3:46 PMNate
12/18/2025, 3:46 PMMike Dedys
12/18/2025, 3:49 PMclass CustomErrorTypeModel(BaseModel):
file: FileStoreResource
error_message: str
error_type: str
class APIResult[T](BaseModel):
data: T | None = None
error: CustomErrorTypeModel | None = NoneMarvin
12/18/2025, 3:50 PMAPIResult[TypeVar] (or even APIResult[User] as a dynamic generic alias), JSON encoding/decoding can fail.
- Pickle handles these cases fine because it doesn’t need to reconstruct the class from an importable name.
Ways to fix it
1) Switch to pickle (easiest)
- This is the default in Prefect 3.x, so if you set result_serializer="json" somewhere, just remove it or set it to pickle explicitly.
from prefect import task
@task(result_serializer="pickle") # or omit; pickle is default in Prefect 3.x
def my_task():
...
- For large results, consider:
@task(result_serializer="compressed/pickle")
2) Keep JSON, return a JSON-able dict
- Dump the model to a dict so the serializer stores plain JSON data.
from prefect import task
@task(result_serializer="json")
def my_task():
result = APIResult<data=User(name="alice", age=30|User>)
return result.model_dump(mode="json") # or result.model_dump()
- If you prefer a JSON string:
return result.model_dump_json()
3) Keep JSON, but use a named concrete subclass (importable)
- Avoid returning a dynamic generic like APIResult[User] and instead define a concrete, top-level subclass so Prefect can re-import it on load.
from pydantic import BaseModel
from typing import Generic, TypeVar
T = TypeVar("T")
class APIResult(BaseModel, Generic[T]):
data: T
status: str = "success"
class User(BaseModel):
name: str
age: int
# Define at module top-level so it's importable
class UserResult(APIResult[User]):
pass
from prefect import task
@task(result_serializer="json")
def my_task() -> UserResult:
return UserResult(data=User(name="alice", age=30))
4) Don’t persist results
- If you don’t need result persistence, you can disable it:
@task(persist_result=False)
def my_task():
return APIResult<...|User>
Helpful docs
- Results and serializers in Prefect 3.x: Prefect results and serializers
A couple of clarifying questions so I can suggest the best fit:
- Do you need JSON-persisted results for downstream systems, or is pickle acceptable?
- Where is result_serializer="json" set (on the task/flow, or via PREFECT_RESULTS_DEFAULT_SERIALIZER)?
- Are your models Pydantic v2 (Prefect 3.x assumes v2)?Mike Dedys
12/18/2025, 4:04 PMMarvin
12/18/2025, 4:04 PMMarvin
12/18/2025, 4:07 PMresult_serializer to pickle (or compressed/pickle).
- Keep JSON but return model_dump() instead of the model instance.
- Use a concrete, importable subclass of your generic model.
If you don’t need persistence, you can set `persist_result=False`; Prefect won’t attempt to serialize on commit, but the in-memory cache will still work during the run.Nate
12/18/2025, 5:37 PM