JSON Schema
Types and utilities for working with JSON Schema in structured output generation.
Schema Interface
type Schema interface {
Validator() Validator
}
Interface for schema definitions that can be used with structured output.
Validator Interface
type Validator interface {
Validate(data interface{}) error
JSONSchema() map[string]interface{}
}
Interface for validating data against a schema.
Creating Schemas
JSONSchemaValidator
func NewJSONSchema(schema map[string]interface{}) *JSONSchemaValidator
Creates a validator from a JSON Schema object.
SimpleJSONSchema
func NewSimpleJSONSchema(schema map[string]interface{}) *SimpleJSONSchema
Creates a simple JSON schema implementation.
StructValidator
func NewStructSchema(targetType reflect.Type) *StructValidator
Creates a validator from a Go struct type.
Examples
Basic JSON Schema
package main
import (
"context"
"fmt"
"log"
"github.com/digitallysavvy/go-ai/pkg/ai"
"github.com/digitallysavvy/go-ai/pkg/providers/openai"
"github.com/digitallysavvy/go-ai/pkg/schema"
)
func main() {
personSchema := schema.NewSimpleJSONSchema(map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"name": map[string]interface{}{
"type": "string",
"description": "Person's full name",
},
"age": map[string]interface{}{
"type": "integer",
"minimum": 0,
"description": "Person's age in years",
},
"email": map[string]interface{}{
"type": "string",
"format": "email",
"description": "Email address",
},
},
"required": []string{"name", "age"},
"additionalProperties": false,
})
p := openai.New(openai.Config{APIKey: "your-api-key"})
model, err := p.LanguageModel("gpt-4")
if err != nil {
log.Fatal(err)
}
// Use with GenerateObject
result, err := ai.GenerateObject(context.Background(), ai.GenerateObjectOptions{
Model: model,
Prompt: "Generate a person",
Schema: personSchema,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("%+v\n", result.Object)
}
Array Schema
tasksSchema := schema.NewSimpleJSONSchema(map[string]interface{}{
"type": "array",
"items": map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"id": map[string]interface{}{"type": "integer"},
"description": map[string]interface{}{"type": "string"},
"completed": map[string]interface{}{"type": "boolean"},
},
"required": []string{"id", "description", "completed"},
},
})
Nested Schema
companySchema := schema.NewSimpleJSONSchema(map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"name": map[string]interface{}{"type": "string"},
"address": map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"street": map[string]interface{}{"type": "string"},
"city": map[string]interface{}{"type": "string"},
"country": map[string]interface{}{"type": "string"},
},
"required": []string{"city", "country"},
},
"employees": map[string]interface{}{
"type": "array",
"items": map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"name": map[string]interface{}{"type": "string"},
"position": map[string]interface{}{"type": "string"},
},
},
},
},
"required": []string{"name"},
})
Enum Schema
statusSchema := schema.NewSimpleJSONSchema(map[string]interface{}{
"type": "string",
"enum": []string{"pending", "in_progress", "completed", "cancelled"},
})
With Patterns and Constraints
userSchema := schema.NewSimpleJSONSchema(map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"username": map[string]interface{}{
"type": "string",
"pattern": "^[a-zA-Z0-9_]{3,20}$",
"minLength": 3,
"maxLength": 20,
},
"password": map[string]interface{}{
"type": "string",
"minLength": 8,
},
"age": map[string]interface{}{
"type": "integer",
"minimum": 13,
"maximum": 120,
},
},
"required": []string{"username", "password"},
})
Using with GenerateObject
recipeSchema := schema.NewSimpleJSONSchema(map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"name": map[string]interface{}{
"type": "string",
"description": "Name of the recipe",
},
"ingredients": map[string]interface{}{
"type": "array",
"description": "List of ingredients",
"items": map[string]interface{}{"type": "string"},
},
"steps": map[string]interface{}{
"type": "array",
"description": "Cooking steps",
"items": map[string]interface{}{"type": "string"},
},
"cookTime": map[string]interface{}{
"type": "integer",
"description": "Cooking time in minutes",
},
},
"required": []string{"name", "ingredients", "steps"},
})
result, err := ai.GenerateObject(ctx, ai.GenerateObjectOptions{
Model: model,
Prompt: "Generate a recipe for chocolate chip cookies",
Schema: recipeSchema,
})
if err != nil {
log.Fatal(err)
}
Validating Data
schema := schema.NewSimpleJSONSchema(personSchema)
data := map[string]interface{}{
"name": "John Doe",
"age": 30,
}
if err := schema.Validator().Validate(data); err != nil {
log.Printf("Validation failed: %v", err)
}
Getting JSON Schema
schema := schema.NewSimpleJSONSchema(personSchema)
jsonSchema := schema.Validator().JSONSchema()
// Returns the original schema map
fmt.Printf("Schema: %+v\n", jsonSchema)
JSON Schema Types
Common JSON Schema property types:
"string"- String values"integer"- Integer numbers"number"- Any numbers (including floats)"boolean"- Boolean values"object"- Nested objects"array"- Arrays of items"null"- Null values
Common Constraints
String Constraints
"minLength": 1,
"maxLength": 100,
"pattern": "^[A-Z][a-z]+$",
"format": "email", // or "uri", "date-time", etc.
Number Constraints
"minimum": 0,
"maximum": 100,
"multipleOf": 5,
Array Constraints
"minItems": 1,
"maxItems": 10,
"uniqueItems": true,
Object Constraints
"required": []string{"field1", "field2"},
"additionalProperties": false,
"minProperties": 1,
"maxProperties": 10,
Applying Schema Defaults
schema.ApplyDefaults returns a copy of a value with JSON Schema object
defaults applied, walking object properties and array items recursively. The
SDK runs this before validation wherever it validates externally-supplied
input against a JSON Schema — MCP structured tool output, ValidateUIMessages,
tool-approval revalidation, agent call options, harness tool ContextSchema,
and workflow.ValidateSerializableToolInput — so a missing field that has a
default in the schema no longer fails validation.
func ApplyDefaults(value interface{}, schema Schema) interface{}
withDefaults := schema.ApplyDefaults(input, mySchema)
if err := mySchema.Validator().Validate(withDefaults); err != nil {
return err
}
Circular $ref Detection
A $ref cycle that never consumes input — for example two schemas that
reference each other only through allOf/anyOf/oneOf/not with no
concrete type in between — now returns a validation error instead of
overflowing the stack. This applies to both Validate and ApplyDefaults.
A not/anyOf/oneOf branch that simply fails to match its subschema is
unaffected; only a genuine reference cycle is rejected.
See Also
- GenerateObject - Structured object generation
- StreamObject - Streaming structured output
- Structured Output Guide