JuliusBrussee/caveman · error
text
Error message
text: %w
What it means
A gemini part type's UnmarshalJSON first decodes into a raw field map, stores it in Extra, then decodes the "text" key into a *string. If "text" is present but is not a JSON string (e.g. an object or number), the error is wrapped as "text: %w" and unmarshaling of the whole part fails.
Solutions
- Ensure each part's "text" is a plain JSON string: {"text": "message"}.
- Check the wrapped error for the JSON type that was found instead of a string.
- If the payload carries structured text, move the structure into a separate field or serialize it to a string first.
Example fix
// before
{"parts": [{"text": {"body": "hi"}}]}
// after
{"parts": [{"text": "hi"}]} Defensive patterns
Strategy: validation
Validate before calling
var probe struct {
Parts []struct {
Text any `json:"text"`
} `json:"parts"`
}
if err := json.Unmarshal(body, &probe); err != nil {
return err
}
for i, p := range probe.Parts {
if _, ok := p.Text.(string); p.Text != nil && !ok {
return fmt.Errorf("part %d: text must be a JSON string", i)
}
} Type guard
func hasStringText(part map[string]json.RawMessage) bool {
v, ok := part["text"]
if !ok {
return true
}
var s string
return json.Unmarshal(v, &s) == nil
} Try / catch
var req geminiGenerateContent
if err := json.Unmarshal(body, &req); err != nil {
if strings.Contains(err.Error(), "text:") {
return fmt.Errorf("a part's \"text\" is not a JSON string: %w", err)
}
return err
} Prevention
- Emit part text as a plain JSON string, never a nested object or number.
- Validate parts with a schema checker before unmarshaling into the typed struct.
- Unmarshal into a permissive map first when porting payloads from other providers, then normalize.
When it happens
Trigger: Unmarshaling a Gemini request/response whose content part contains "text" with a non-string value — e.g. {"text": {"value": "hi"}} or {"text": 42} — anywhere TransformGemini or response handling decodes parts.
Common situations: Migrating payloads from providers where content blocks are objects; code that accidentally nests the string under an object; generated clients emitting typed text fields.
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
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/5ad54a1ba1278846.
Report an issue: GitHub.
Appendix: source
Thrown at engine/pixel/transform_gemini.go:351
b, err := json.Marshal(req.Tools)
if err != nil {
return nil, err
}
raw["tools"] = b
}
return json.Marshal(raw)
}
func (p *geminiPart) UnmarshalJSON(data []byte) error {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return err
}
p.Extra = raw
if v, ok := raw["text"]; ok {
var s string
if err := json.Unmarshal(v, &s); err != nil {
return fmt.Errorf("text: %w", err)
}
p.Text = &s
}
if v, ok := raw["inline_data"]; ok {
var inline geminiInlineData
if err := json.Unmarshal(v, &inline); err != nil {
return fmt.Errorf("inline_data: %w", err)
}
p.InlineData = &inline
}
if v, ok := raw["functionResponse"]; ok {
var fr geminiFunctionResponse
if err := json.Unmarshal(v, &fr); err != nil {
return fmt.Errorf("functionResponse: %w", err)
}
p.FunctionResponse = &fr
}
if v, ok := raw["functionCall"]; ok {View on GitHub (pinned to 3ee70a1026)