Schema Validation Errors
This guide covers JSON schema validation errors when generating structured data with the Go AI SDK.
Schema Definition Errors
Error: "Invalid Schema Format"
Symptoms:
Error: schema validation failed: invalid type definition
Error: missing required field in schema
Cause: Incorrectly formatted JSON schema or missing required fields.
Solution:
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"os"
"github.com/digitallysavvy/go-ai/pkg/ai"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
goschema "github.com/digitallysavvy/go-ai/pkg/schema"
"github.com/invopop/jsonschema"
)
// Define struct with comprehensive JSON schema tags
type Recipe struct {
Name string `json:"name" jsonschema:"required,minLength=1,maxLength=100,description=Recipe name"`
Description string `json:"description" jsonschema:"required,minLength=10,description=Recipe description"`
Ingredients []string `json:"ingredients" jsonschema:"required,minItems=1,description=List of ingredients"`
Steps []string `json:"steps" jsonschema:"required,minItems=1,description=Cooking instructions"`
PrepTime int `json:"prepTime" jsonschema:"required,minimum=1,description=Preparation time in minutes"`
CookTime int `json:"cookTime" jsonschema:"required,minimum=1,description=Cooking time in minutes"`
Servings int `json:"servings" jsonschema:"required,minimum=1,maximum=20,description=Number of servings"`
Difficulty string `json:"difficulty" jsonschema:"required,enum=easy,enum=medium,enum=hard,description=Recipe difficulty level"`
Cuisine string `json:"cuisine" jsonschema:"description=Type of cuisine"`
IsVegetarian bool `json:"isVegetarian" jsonschema:"description=Whether the recipe is vegetarian"`
}
func main() {
ctx := context.Background()
provider := openai.New(openai.Config{
APIKey: os.Getenv("OPENAI_API_KEY"),
})
model, _ := provider.LanguageModel("gpt-4")
// Generate schema with proper configuration
reflector := jsonschema.Reflector{
AllowAdditionalProperties: false, // Strict validation
DoNotReference: true, // Inline definitions
RequiredFromJSONSchemaTags: true, // Use jsonschema tags
}
reflected := reflector.Reflect(&Recipe{})
// Validate schema before using
schemaBytes, err := json.MarshalIndent(reflected, "", " ")
if err != nil {
log.Fatalf("Invalid schema: %v", err)
}
log.Printf("Generated schema:\n%s", schemaBytes)
// ai.GenerateObjectOptions.Schema takes a schema.Schema, not a
// *jsonschema.Schema directly — decode the reflected schema into a map
// and wrap it with goschema.NewSimpleJSONSchema.
var schemaMap map[string]interface{}
if err := json.Unmarshal(schemaBytes, &schemaMap); err != nil {
log.Fatalf("Failed to decode schema: %v", err)
}
var recipe Recipe
if err := ai.GenerateObjectInto(ctx, ai.GenerateObjectOptions{
Model: model,
Schema: goschema.NewSimpleJSONSchema(schemaMap),
Prompt: "Create a lasagna recipe for 4 people",
}, &recipe); err != nil {
log.Fatalf("Failed to generate object: %v", err)
}
fmt.Printf("Recipe: %s\n", recipe.Name)
fmt.Printf("Prep time: %d minutes\n", recipe.PrepTime)
fmt.Printf("Servings: %d\n", recipe.Servings)
}
Type Mismatch Errors
Error: "Generated Object Type Mismatch"
Symptoms:
Error: json: cannot unmarshal string into Go value of type int
Error: field type mismatch
Cause: Model generates JSON that doesn't match your struct types.
Solution:
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"os"
"strconv"
"github.com/digitallysavvy/go-ai/pkg/ai"
"github.com/digitallysavvy/go-ai/pkg/provider"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
goschema "github.com/digitallysavvy/go-ai/pkg/schema"
"github.com/invopop/jsonschema"
)
// Custom type with JSON unmarshaling
type FlexibleInt int
func (f *FlexibleInt) UnmarshalJSON(data []byte) error {
// Try to unmarshal as int first
var i int
if err := json.Unmarshal(data, &i); err == nil {
*f = FlexibleInt(i)
return nil
}
// If that fails, try as string
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
// Convert string to int
i, err := strconv.Atoi(s)
if err != nil {
return fmt.Errorf("cannot convert %q to int: %w", s, err)
}
*f = FlexibleInt(i)
return nil
}
// Product with flexible types
type Product struct {
Name string `json:"name" jsonschema:"required"`
Price FlexibleInt `json:"price" jsonschema:"required,type=integer,description=Price in cents"`
Quantity FlexibleInt `json:"quantity" jsonschema:"required,type=integer,description=Quantity available"`
InStock bool `json:"inStock" jsonschema:"required"`
Description string `json:"description" jsonschema:"required"`
}
// Validation after unmarshaling
func (p *Product) Validate() error {
if p.Name == "" {
return fmt.Errorf("name is required")
}
if p.Price < 0 {
return fmt.Errorf("price must be non-negative")
}
if p.Quantity < 0 {
return fmt.Errorf("quantity must be non-negative")
}
return nil
}
func generateProduct(ctx context.Context, model provider.LanguageModel, prompt string) (*Product, error) {
reflector := jsonschema.Reflector{
AllowAdditionalProperties: false,
DoNotReference: true,
}
reflected := reflector.Reflect(&Product{})
schemaBytes, err := json.Marshal(reflected)
if err != nil {
return nil, fmt.Errorf("invalid schema: %w", err)
}
var schemaMap map[string]interface{}
if err := json.Unmarshal(schemaBytes, &schemaMap); err != nil {
return nil, fmt.Errorf("failed to decode schema: %w", err)
}
var product Product
if err := ai.GenerateObjectInto(ctx, ai.GenerateObjectOptions{
Model: model,
Schema: goschema.NewSimpleJSONSchema(schemaMap),
Prompt: prompt,
}, &product); err != nil {
return nil, fmt.Errorf("generation failed: %w", err)
}
// Validate after unmarshaling
if err := product.Validate(); err != nil {
return nil, fmt.Errorf("validation failed: %w", err)
}
return &product, nil
}
func main() {
ctx := context.Background()
provider := openai.New(openai.Config{
APIKey: os.Getenv("OPENAI_API_KEY"),
})
model, _ := provider.LanguageModel("gpt-4")
product, err := generateProduct(ctx, model, "Create a laptop product listing")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Product: %s - $%.2f\n", product.Name, float64(product.Price)/100)
}
Nested Structure Errors
Error: "Complex Nested Schema Validation Failed"
Symptoms:
Error: nested object validation failed
Error: array item schema mismatch
Cause: Complex nested structures not properly defined in schema.
Solution:
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"os"
"github.com/digitallysavvy/go-ai/pkg/ai"
"github.com/digitallysavvy/go-ai/pkg/provider"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
goschema "github.com/digitallysavvy/go-ai/pkg/schema"
"github.com/invopop/jsonschema"
)
// Address is a nested structure
type Address struct {
Street string `json:"street" jsonschema:"required,description=Street address"`
City string `json:"city" jsonschema:"required,description=City name"`
State string `json:"state" jsonschema:"required,pattern=^[A-Z]{2}$,description=Two-letter state code"`
ZipCode string `json:"zipCode" jsonschema:"required,pattern=^[0-9]{5}$,description=Five-digit ZIP code"`
}
// OrderItem represents a single item in an order
type OrderItem struct {
ProductID string `json:"productId" jsonschema:"required,description=Product identifier"`
ProductName string `json:"productName" jsonschema:"required,description=Product name"`
Quantity int `json:"quantity" jsonschema:"required,minimum=1,description=Quantity ordered"`
UnitPrice float64 `json:"unitPrice" jsonschema:"required,minimum=0,description=Price per unit"`
Subtotal float64 `json:"subtotal" jsonschema:"required,minimum=0,description=Line item total"`
}
// Order is the top-level structure with nested objects
type Order struct {
OrderID string `json:"orderId" jsonschema:"required,description=Unique order identifier"`
CustomerName string `json:"customerName" jsonschema:"required,description=Customer full name"`
CustomerEmail string `json:"customerEmail" jsonschema:"required,format=email,description=Customer email address"`
ShippingAddr Address `json:"shippingAddress" jsonschema:"required,description=Shipping address"`
BillingAddr Address `json:"billingAddress" jsonschema:"required,description=Billing address"`
Items []OrderItem `json:"items" jsonschema:"required,minItems=1,description=Order items"`
Subtotal float64 `json:"subtotal" jsonschema:"required,minimum=0,description=Subtotal before tax"`
Tax float64 `json:"tax" jsonschema:"required,minimum=0,description=Tax amount"`
ShippingCost float64 `json:"shippingCost" jsonschema:"required,minimum=0,description=Shipping cost"`
Total float64 `json:"total" jsonschema:"required,minimum=0,description=Total amount"`
Status string `json:"status" jsonschema:"required,enum=pending,enum=processing,enum=shipped,enum=delivered,description=Order status"`
}
// Validate ensures the order data is consistent
func (o *Order) Validate() error {
// Validate items
if len(o.Items) == 0 {
return fmt.Errorf("order must have at least one item")
}
// Calculate and verify subtotal
calculatedSubtotal := 0.0
for i, item := range o.Items {
if item.Quantity <= 0 {
return fmt.Errorf("item %d: quantity must be positive", i)
}
if item.UnitPrice < 0 {
return fmt.Errorf("item %d: price cannot be negative", i)
}
expectedSubtotal := float64(item.Quantity) * item.UnitPrice
if item.Subtotal != expectedSubtotal {
log.Printf("Warning: item %d subtotal mismatch (expected %.2f, got %.2f)",
i, expectedSubtotal, item.Subtotal)
}
calculatedSubtotal += item.Subtotal
}
// Verify order totals
if o.Subtotal != calculatedSubtotal {
log.Printf("Warning: order subtotal mismatch (expected %.2f, got %.2f)",
calculatedSubtotal, o.Subtotal)
}
expectedTotal := o.Subtotal + o.Tax + o.ShippingCost
if o.Total != expectedTotal {
log.Printf("Warning: order total mismatch (expected %.2f, got %.2f)",
expectedTotal, o.Total)
}
return nil
}
func generateOrder(ctx context.Context, model provider.LanguageModel) (*Order, error) {
reflector := jsonschema.Reflector{
AllowAdditionalProperties: false,
DoNotReference: true,
RequiredFromJSONSchemaTags: true,
}
reflected := reflector.Reflect(&Order{})
// Log schema for debugging
schemaJSON, _ := json.MarshalIndent(reflected, "", " ")
log.Printf("Schema:\n%s", schemaJSON)
var schemaMap map[string]interface{}
if err := json.Unmarshal(schemaJSON, &schemaMap); err != nil {
return nil, fmt.Errorf("failed to decode schema: %w", err)
}
prompt := `Generate a sample order for Jane Smith (jane@example.com) who ordered:
- 2x Laptop at $999.99 each
- 1x Mouse at $29.99
Ship to: 123 Main St, Boston, MA 02101
Same billing address
Tax rate: 6.25%
Shipping: $15.00`
var order Order
if err := ai.GenerateObjectInto(ctx, ai.GenerateObjectOptions{
Model: model,
Schema: goschema.NewSimpleJSONSchema(schemaMap),
Prompt: prompt,
}, &order); err != nil {
return nil, fmt.Errorf("generation failed: %w", err)
}
// Validate the order
if err := order.Validate(); err != nil {
return nil, fmt.Errorf("validation failed: %w", err)
}
return &order, nil
}
func main() {
ctx := context.Background()
provider := openai.New(openai.Config{
APIKey: os.Getenv("OPENAI_API_KEY"),
})
model, _ := provider.LanguageModel("gpt-4")
order, err := generateOrder(ctx, model)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Order %s for %s\n", order.OrderID, order.CustomerName)
fmt.Printf("Items: %d\n", len(order.Items))
fmt.Printf("Total: $%.2f\n", order.Total)
}
Array Validation Errors
Error: "Array Items Don't Match Schema"
Symptoms:
Error: array item validation failed at index 2
Error: array contains items of wrong type
Cause: Array items don't conform to the specified item schema.
Solution:
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"os"
"github.com/digitallysavvy/go-ai/pkg/ai"
"github.com/digitallysavvy/go-ai/pkg/provider"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
goschema "github.com/digitallysavvy/go-ai/pkg/schema"
"github.com/invopop/jsonschema"
)
// Task with validation
type Task struct {
ID string `json:"id" jsonschema:"required,pattern=^TASK-[0-9]+$,description=Task ID in format TASK-XXX"`
Title string `json:"title" jsonschema:"required,minLength=5,maxLength=100,description=Task title"`
Description string `json:"description" jsonschema:"required,minLength=10,description=Detailed task description"`
Priority string `json:"priority" jsonschema:"required,enum=low,enum=medium,enum=high,enum=urgent,description=Task priority"`
Status string `json:"status" jsonschema:"required,enum=todo,enum=in_progress,enum=done,description=Task status"`
Assignee string `json:"assignee" jsonschema:"required,minLength=1,description=Person assigned to task"`
DueDate string `json:"dueDate" jsonschema:"required,format=date,description=Due date in YYYY-MM-DD format"`
}
// TaskList with array validation
type TaskList struct {
ProjectName string `json:"projectName" jsonschema:"required,description=Project name"`
Tasks []Task `json:"tasks" jsonschema:"required,minItems=3,maxItems=10,description=List of tasks"`
}
// Validate all tasks in the list
func (tl *TaskList) Validate() error {
if len(tl.Tasks) == 0 {
return fmt.Errorf("task list cannot be empty")
}
taskIDs := make(map[string]bool)
for i, task := range tl.Tasks {
// Check for duplicate IDs
if taskIDs[task.ID] {
return fmt.Errorf("duplicate task ID at index %d: %s", i, task.ID)
}
taskIDs[task.ID] = true
// Validate individual task
if err := validateTask(&task, i); err != nil {
return err
}
}
return nil
}
func validateTask(task *Task, index int) error {
if task.Title == "" {
return fmt.Errorf("task %d: title is required", index)
}
if task.Description == "" {
return fmt.Errorf("task %d: description is required", index)
}
validPriorities := map[string]bool{"low": true, "medium": true, "high": true, "urgent": true}
if !validPriorities[task.Priority] {
return fmt.Errorf("task %d: invalid priority %q", index, task.Priority)
}
validStatuses := map[string]bool{"todo": true, "in_progress": true, "done": true}
if !validStatuses[task.Status] {
return fmt.Errorf("task %d: invalid status %q", index, task.Status)
}
return nil
}
func generateTaskList(ctx context.Context, model provider.LanguageModel) (*TaskList, error) {
reflector := jsonschema.Reflector{
AllowAdditionalProperties: false,
DoNotReference: true,
RequiredFromJSONSchemaTags: true,
}
reflected := reflector.Reflect(&TaskList{})
schemaBytes, err := json.Marshal(reflected)
if err != nil {
return nil, fmt.Errorf("invalid schema: %w", err)
}
var schemaMap map[string]interface{}
if err := json.Unmarshal(schemaBytes, &schemaMap); err != nil {
return nil, fmt.Errorf("failed to decode schema: %w", err)
}
var taskList TaskList
if err := ai.GenerateObjectInto(ctx, ai.GenerateObjectOptions{
Model: model,
Schema: goschema.NewSimpleJSONSchema(schemaMap),
Prompt: "Generate a task list for a website redesign project with 5 tasks",
}, &taskList); err != nil {
return nil, fmt.Errorf("generation failed: %w", err)
}
// Validate the task list
if err := taskList.Validate(); err != nil {
return nil, fmt.Errorf("validation failed: %w", err)
}
return &taskList, nil
}
func main() {
ctx := context.Background()
provider := openai.New(openai.Config{
APIKey: os.Getenv("OPENAI_API_KEY"),
})
model, _ := provider.LanguageModel("gpt-4")
taskList, err := generateTaskList(ctx, model)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Project: %s\n", taskList.ProjectName)
fmt.Printf("Tasks: %d\n", len(taskList.Tasks))
for i, task := range taskList.Tasks {
fmt.Printf("%d. [%s] %s (Priority: %s, Assignee: %s)\n",
i+1, task.Status, task.Title, task.Priority, task.Assignee)
}
}
Best Practices
1. Use Comprehensive JSON Schema Tags
type Field struct {
Name string `json:"name" jsonschema:"required,minLength=1,maxLength=100,description=Field name"`
}
2. Always Validate After Unmarshaling
var result MyType
json.Unmarshal(data, &result)
if err := result.Validate(); err != nil {
return fmt.Errorf("validation failed: %w", err)
}
3. Log Raw JSON on Errors
When the model's output doesn't parse against the schema, ai.GenerateObject
and ai.GenerateObjectInto return an *ai.NoObjectGeneratedError, whose
Text field carries the model's raw (unparseable) JSON response:
result, err := ai.GenerateObject(ctx, opts)
if err != nil {
var noObjErr *ai.NoObjectGeneratedError
if errors.As(err, &noObjErr) {
log.Printf("Raw JSON: %s", noObjErr.Text)
}
return err
}
4. Use Flexible Types When Needed
type FlexibleType struct {
Value interface{} `json:"value"`
}
func (f *FlexibleType) UnmarshalJSON(data []byte) error {
// Custom unmarshaling logic
}
5. Test Schema Generation
schema := reflector.Reflect(&YourType{})
schemaJSON, _ := json.MarshalIndent(schema, "", " ")
log.Printf("Generated schema:\n%s", schemaJSON)